Changelog
Every released version of visvoai-ai and visvoai-core. Rendered from the same CHANGELOG.md files that ship with the packages, so this page cannot fall out of step with a release.
Looking for the terminal agent? visvoai-cli has its own changelog.
All notable changes to this package. Versions follow v0.MINOR.PATCH while the API is
unstable (pre-1.0): MINOR for new capability or breaking changes, PATCH for fixes. No
major (1.0) bump until the surface stabilizes.
- 0.4.2Latest
Added
-
DeploymentInfo.vendor/.vendor_label— who made a model, as distinct fromprovider, who serves it. Three things get called "provider" and are not the same:| | | | |---|---|---| | vendor | who made it |
google| | provider | who serves it (the route) |geminidirect, oropenrouter| | slug | what that route expects |gemini-3.7-flash/google/gemini-3.7-flash|A picker groups by vendor: "the Google models" is a meaningful heading, while "the OpenRouter models" is 340 models from thirty vendors. Grouping by
providerwould also file the same model under two headings.Derived, not stored, from
(provider, slug): aggregators namespace their slugsvendor/model, first-party routes map from the provider name. The normalisation is real — the same vendor appears asqwenandQwen, asmeta-llamaandmeta, and OpenRouter prefixes some routes with~. ReturnsNonerather than guessing, so an unknown vendor renders ungrouped instead of under a wrong heading (3 of 453 on the live catalog, all Groq slugs carrying no namespace). -
DeploymentInfo.icon_url— the provider logo, for a consumer's model picker.Nonewhen the source supplied none; whoever renders it owns the fallback.Carried on
Deploymentand on the public projection, with a test covering the fullModelDefinition → Deployment → DeploymentInfochain — this is the same hopstatussilently failed to make in 0.4.0.
Fixed
-
Models were offered thinking levels their API rejects.
thinking_levelsreturned all four levels for anything withsupports_thinking, treating the level set as a property of the mechanism. It is a property of the model:gemini-3.7-flashandgemini-3.1-pro-previewreject"minimal"— whatOFFmaps to — with a 400, whilegemini-3.6-flashandgemini-3.5-flashaccept it. Verified against the live API, model by model, not inferred.ModelDefinition.thinking_levels(and a correction for the two Gemini models) narrows the set.Nonestill means all four. -
A model could default to a level it rejects. The default and the allowed set are independent facts, and
gemini-3.1-pro-previewhad them contradict: its label resolved toOFFwhile the correction removedminimal. That is the worse form of the bug — it needs no user action, so every turn on that model would 400._default_level()now reconciles toward the narrowest level the model does accept, and a test asserts no deployment can default outside its own set. -
Every models.dev model carried the Google favicon as its logo. The adapter never set
icon_url, so all ~5,400 catalog-sourced definitions inheritedModelDefinition's default — which is Google's. A picker built on the catalog showed one provider's icon against every model in it.Definitions now carry
https://models.dev/logos/<provider>.svg, keyed by the provider's upstream id. That distinction is the fix:PROVIDER_ALIASrenamesgoogle→geminiandtogetherai→together, neither of which exists upstream, and the logo endpoint soft-404s — an unknown id answers200with a placeholder rather than an error. Using the aliased name would have produced a plausible URL that silently rendered the wrong image.Against the live catalog this yields 169 distinct logos across 5,483 definitions, with none left on the Google default.
-
Fixed
-
statusnever reachedDeploymentInfo. 0.4.0 added it toModelDefinitionand toDeployment— the internal record — but not to the public read-only projection, which is "the ONLY model-data type consumers touch". So the tag existed, the data flowed, and every consumer readinginfo.statusgotAttributeError. The feature was unusable as shipped.A regression test now asserts the field survives the hop to
DeploymentInfoand is populated, not merely declared.
-
Gemini facts now come from models.dev instead of being maintained by hand, and a pricing error that hand-maintenance had been hiding is fixed.
Fixed
-
Gemini
cache_read_cost_per_millionwas 2.5x too high on all 14 models. Google prices context-cache reads at 10% of input (https://ai.google.dev/gemini-api/docs/pricing); this registry carried 25%.| model | was | now | |---|---|---| |
gemini-3.7-flash| 0.1875 | 0.075 | |gemini-3.5-flash| 0.375 | 0.15 | |gemini-2.5-pro| 0.3125 | 0.125 | | …11 others | | |Reporting only —
estimated_cost_usdwas inflated wherever cached tokens were recorded; it never changed what a provider billed.This is a correction, not an update to new pricing. models.dev carried the same wrong figures and fixed them on 2026-04-20 — commit "[google] Fix cache_read cost in gemini-2.5-flash model" moved 0.075 → 0.03, and a sibling commit moved gemini-2.5-pro 0.31 → 0.125. Those pre-fix values are exactly what this registry still carried. Google did not change its pricing; a shared error was fixed upstream four months ago and not here.
Correcting the 0.2.4 notes: they called the 25% figure a deliberate convention and used it to justify overriding models.dev, which had the correct 10%. That was wrong. The gap was uniform across every Gemini row and uniformity was mistaken for intent. A regression test now asserts the 10% relationship, and the module docstring says to verify against the pricing page rather than against the rest of the file — internal consistency is what disguised this.
Added
build_catalog(..., corrections=...)— per-field overlays,{(provider, api_id): {field: value}}, applied after the merge. Deliberately not aCatalogSource: a source yields whole models and merge is "later wins wholesale", so curation shipped as a source would clobber the live facts it is meant to sit on. An unknown field raises (a typo that silently did nothing would look applied); an unknown model id is logged and ignored (upstream dropping a model is stale, not fatal).catalog.corrections.CURATED_CORRECTIONS— the handful of things models.dev does not model. Verified against its schema, not guessed:Costis strictly per-token, so Google Search grounding (billed per query) has nowhere to live; there is no capability concept beyondtool_call/reasoning; and no thinking-default notion.ModelDefinition.status/DeploymentInfo.status— upstream lifecycle,"alpha" | "beta" | None. Presentation only: such models stay fully selectable, for consumers to surface as a tag. Retirement isdeprecated, a separate axis.
Changed
-
Gemini is sourced from models.dev.
googleleft the adapter'sBESPOKE_OR_DENYset, where it sat beside Bedrock and Azure for a different reason — those are uncallable, Gemini was merely curated elsewhere. First-party providers now take a path that leavesbase_url/key_envunset and lets the static provider config resolve them.35 Gemini models instead of 18.
gemini-3.6-flashandgemini-3.7-flashwould have arrived on their own; they were added by hand in 0.2.4, which is what prompted this.The baked source is not deleted — three models it carries are absent upstream (
gemini-2.5-flash-preview,gemini-2.5-flash-lite-preview-09-2025,imagen-4.0-fast-generate-001) and survive untouched. -
status: "deprecated"from models.dev now setsdeprecated=True— 128 retired models across the catalog. They are excluded fromlist_deployments()anddefault_deployment()so they can never be picked for new work, but stay inMODEL_PRICING_MAPso anllm_call_logsrow naming one still prices. Dropping them would have made historical spend unreadable.Previously the adapter ignored
statusentirely and every retired model arrived selectable.
-
One theme: this package holds facts about models. Which model is default, and whether "deep research" exists, are not facts — they belong to the consumer or to nothing at all. Its own docstring already said so; the code did not.
Added
-
set_default_deployment(capability, deployment_id)andget_default_overrides(). A consumer now chooses its own default; the package keeps a fallback sopip install visvoai-aistill works standalone.Resolution is now: consumer override → curated
DEFAULT_MODEL_FOR→ thedefault=Truemodel → first enabled.Deliberately module-level, not registry state:
install_catalog()builds a fresh registry, so an override stored on the instance would be silently wiped by a consumer that set its default before installing a catalog.Validated when set, not at first use — an unknown id, or one that does not declare the capability, raises where the caller's stack still points at the line that set it. A
providerfilter also ignores a foreign override: asking for the default Anthropic chat model must not return a Gemini one.Before this, changing a default required a package release. It just did, twice.
Removed
-
BREAKING:
Capability.DEEP_RESEARCH. Deep research is not something a model does. It is a separate agent —deep-research-preview-04-2026anddeep-research-max-preview-04-2026— invoked withagent=rather thanmodel=, and only through the Interactions API; Google's docs state it "cannot be accessed throughgenerate_content".The capability was declared by
gemini-3-flash-preview, named inDEFAULT_MODEL_FOR, and read by nothing. It asserted something untrue of every Gemini chat model.Consumers that implement deep research should name the agent directly, which is what the one known consumer already does.
Changed
-
gemini-3.7-flashis the default model, takingdefault=Truefromgemini-3-flash-preview. Google documents it as "our latest and most capable Flash model, built for complex coding, agentic workflows, and reliable multi-step execution".It also takes
default_thinking_label="Think". Without that the switch would have been a silent regression: the old default resolves to MEDIUM thinking andgemini-3.7-flashhad no label, so every new chat would have dropped to thinking OFF — invisible in any diff of "which model is default".It is not cheaper than the model it replaces: $0.75/$3.75 against $0.50/$3.00, so +50% input and +25% output per token.
-
test_list_deployments_filters_and_defaultasserted a literal model id while its own comment said it checked "the registry default model's deployment". It now derives the expectation from the registry, testing the rule rather than today's pick.
-
Added
-
gemini-3.7-flashandgemini-3.6-flashto the model registry. Both carry a 1,048,576-token context, tool calling and thinking, and are registered forCHATandSEARCH.Context window, input and output rates come from models.dev rather than being typed by hand, since a wrong
input_cost_per_millionsilently corrupts every cost figure derived from it:| model | input | output | cache read | |---|---|---|---| |
gemini-3.7-flash| $0.75 | $3.75 | $0.1875 | |gemini-3.6-flash| $1.50 | $7.50 | $0.375 |cache_read_cost_per_milliondeliberately does not follow models.dev. This registry prices cache reads at 25% of input, per the Gemini Developer API pricing page named in the module docstring; models.dev reports 10%. That gap is uniform — exactly 2.5x across all seven existing Gemini entries — so it is a difference of convention, not a per-model error, and a new model following the other convention would have been the only inconsistent row in the table.search_query_costis mirrored fromgemini-3.5-flash— models.dev does not carry grounding pricing, and it is a provider-level rate rather than a per-model one.No default changed:
DEFAULT_MODEL_FORstill pointsSEARCHandDEEP_RESEARCHatgemini-3-flash-preview.
-
Fixed
- Reasoning models on OpenAI-compatible providers (Together/OpenRouter/…) no longer
hit the OpenAI Responses API. langchain-openai auto-switches to
/responseswhen a top-levelreasoningdict is present; those providers reject it (400 Invalid Responses API request) or return block-list content that breaks the next turn.OPENROUTER_REASONINGnow sendsreasoningviaextra_body, andOpenAICompatProvider.buildpinsuse_responses_api=Falsefor non-OpenAI providers.
- Reasoning models on OpenAI-compatible providers (Together/OpenRouter/…) no longer
hit the OpenAI Responses API. langchain-openai auto-switches to
Fixed
build_catalogdrops models whose id can't round-trip through the identity codec (e.g. cloudflare's@cf/…slugs, which collide with the@effortmarker). They previously listed but crashedget_deployment— a landmine in any picker.
Fixed
resolve_api_keynow cleans keys (strips whitespace + a layer of wrapping quotes) at the single resolution chokepoint — covering explicit args,env_var, and the static map. A key with a trailing space/newline or wrapped in quotes (common from shells,.env, or config) was sent verbatim and silently rejected as401 User not found; it's now normalized before the request.
Added
- Catalog engine (
catalog/):CatalogSourceABC,BakedSource,build_catalog()(merge → gate → validate). Output islist[ModelDefinition]— a drop-in for the static registry list. - models.dev adapter (
catalog.sources.modelsdev):to_definitions()/ModelsDevSourcemap the live models.dev catalog intoModelDefinitions. Admission is callability-based (derivable Chat Completions base_url + not bespoke/denied) — ~4150 defs / ~128 providers. - Remote source (
catalog.sources.remote.RemoteModelsDevSource): cached, offline-tolerant models.dev fetch (stdlib only). Degrades fresh-cache → fetch → stale-cache → bundled snapshot → empty; never raises. - Bundled snapshot:
catalog/data/modelsdev_snapshot.json.gz(generated, ~190 KB) as the OpenAI-compat offline floor, plusscripts/generate_modelsdev_snapshot.py(deterministic). DeploymentRegistry: instance-scoped Model/Deployment view;install_catalog()/set_default_registry()swap the module default — the dynamic-catalog seam.ModelDefinition/Deploymentcarrybase_url+key_env;build_chat_modelthreads them so catalog-sourced (non-statically-wired) providers are self-contained.ThinkingMechanism.ANTHROPIC_ADAPTIVEfor Claude 4.6+ ({"type":"adaptive"}); legacyANTHROPIC_BUDGETretained for ≤4.5.resolve_api_key(provider, env_var=…).
Notes
- Registry
supports_thinkingstaysFalsefor Claude — the resolver dialect is correct but live-unverified against the Anthropic API.
- Catalog engine (
- Initial Model/Deployment registry, identity codec, per-provider thinking, provider facades.
Versions follow v0.MINOR.PATCH while unstable (pre-1.0): MINOR for new
capability or breaking changes, PATCH for fixes.
- 0.4.0Latest
Added
[sqlite]extra —pip install "visvoai-core[sqlite]"pullsaiosqlite+langgraph-checkpoint-sqlite>=3.0.0for durable conversation memory viaAsyncSqliteSaver. The floor makes pip enforce a checkpoint-package pair that actually works together; previously, hand-installing newest-of-each could produce an incompatible combination that only failed at import time (found by a contributor testing the upcoming durable-memory example in a fresh environment — thanks @DwitiThaker).
Added
- Tool intake normalization (
visvoai.core.adapt):build_graphnow accepts plain typed Python functions (sync or async — schema from type hints, description from the docstring),BaseAgentToolclasses/instances (executed through the persistence lifecycle), and LangChainBaseTools, mixed freely in one list.as_tool/as_tools/as_tools_mapexported. ask(graph, text, thread_id=None)— the text boundary over the graph's invoke contract: LangGraph/LangChain message shapes stay internal for request/response callers;astream_eventsremains the streaming surface.AgentRuntime.build_graphmatches the core builder:all_tools_mapoptional,core_toolsaccepts every tool shape (was still typed/required as LangChain-only at the runtime seam).- Plain-function tools: a Google-style
Args:docstring section becomes per-argument descriptions in the model-facing schema. all_tools_mapis now optional — derived fromcore_toolswhen omitted.
- Tool intake normalization (
Added
AgentRuntime._get_state_class()— extendAgentState(TypedDict inheritance) with your own fields and have them flow through the graph, without overridingbuild_graph. Closes the gap between the documented seam ("extend AgentState") and reality (the state schema was hardcoded).- A real test suite (30 tests): the loop's behavioral contract (routing,
parallel tool calls, system-prompt injection), the soft step cap (clean
finalize + the pathological case), per-round retrieval binding, every
runtime hook from a consumer's seat (extend/replace nodes, routing,
checkpointer, interrupts, state extension), the
BaseAgentToollifecycle, andToolCatalogranking/hybrid quality.
Fixed
- A pathological model can no longer loop past the step cap. The cap's
finalize round runs unbound (no tool declarations), so a well-formed model
must answer — but a malformed provider that hallucinates tool calls anyway
was routed back into the loop until the recursion limit.
should_continuenow forces END past the cap.
Initial extraction from the platform:
AgentRuntime+ hooks, the core agent→tools graph with soft step cap,BaseAgentToollifecycle +tool_configregistration,ToolPersistence/LLMPersistenceseams,RuntimeContext, semantic tool retrieval (ToolCatalog).