All notable changes to this project will be documented in this file.
- Vertex AI v1/v1beta1 bug:
Model.parse("vertex_ai:gemini-2.5-pro-preview-06-05")withGOOGLE_CLOUD_PROJECTset was storing a hardcodedv1URL inmodel.base_url, causing the provider'sv1beta1selection logic to be bypassed. Preview models now correctly usev1beta1at request time.
- Vertex AI input validation: Project ID and region from environment variables are now validated with helpful error messages instead of producing opaque DNS/HTTP errors.
GOOGLE_CLOUD_LOCATIONsupport: Added as a fallback forGOOGLE_CLOUD_REGION, consistent with other Google Cloud libraries and tooling.- Multi-region example script:
examples/providers/vertex_ai_multi_region.exs
- Vertex AI model routing: Fixed
build_request_params/3not including the"model"key in the params map, causingchat/2andchat_stream/2to always fall back to"gemini-2.0-flash"regardless of the requested model. - Vertex AI 404 on preview models: Use
v1beta1API version for preview and experimental models (e.g.,gemini-3.1-pro-preview). Thev1endpoint returns 404 for these models.
Nous.Providers.VertexAI.api_version_for_model/1— returns"v1beta1"for preview/experimental models,"v1"for stable models.Nous.Providers.VertexAI.endpoint/3now accepts an optional model name to select the correct API version.- Debug logging for Vertex AI request URLs.
- Auto-update memory:
Nous.Plugins.Memorycan now automatically reflect on conversations and update memories after each run — no explicit tool calls needed. Enable withauto_update_memory: trueinmemory_config. Configurable reflection model, frequency, and context limits.- New
after_run/3callback inNous.Pluginbehaviour — runs once after the entire agent run completes. Wired into bothAgentRunner.run/3andrun_with_context/3. Nous.Plugin.run_after_run/4helper for executing the hook across all plugins- New config options:
:auto_update_memory,:auto_update_every,:reflection_model,:reflection_max_tokens,:reflection_max_messages,:reflection_max_memories - New example:
examples/memory/auto_update.exs
- New
- Vertex AI provider:
Nous.Providers.VertexAIfor accessing Gemini models through Google Cloud Vertex AI. Supports enterprise features (VPC-SC, CMEK, regional endpoints, IAM).- Three auth modes: app config Goth (
config :nous, :vertex_ai, goth: MyApp.Goth), per-model Goth (default_settings: %{goth: MyApp.Goth}), or direct access token (api_key/VERTEX_AI_ACCESS_TOKEN) - Bearer token auth via
api_keyoption,VERTEX_AI_ACCESS_TOKENenv var, or Goth integration - Goth integration (
{:goth, "~> 1.4", optional: true}) for automatic service account token management — reuse existing Goth processes from PubSub, etc. - URL auto-construction from
GOOGLE_CLOUD_PROJECTandGOOGLE_CLOUD_REGIONenv vars Nous.Providers.VertexAI.endpoint/2helper to build endpoint URLs- Reuses existing Gemini message format, response parsing, and stream normalization
- Model string:
"vertex_ai:gemini-2.0-flash"
- Three auth modes: app config Goth (
- Gemini streaming: Fixed streaming responses returning 0 events. The Gemini
streamGenerateContentendpoint returns a JSON array (application/json) by default, not Server-Sent Events. Instead of forcing SSE viaalt=ssequery parameter, added a pluggable stream parser toNous.Providers.HTTP.
Nous.Providers.HTTP.JSONArrayParser— stream buffer parser for JSON array responses. Extracts complete JSON objects from a streaming[{...},{...},...]response by tracking{}nesting depth while respecting string literals and escape sequences.:stream_parseroption onHTTP.stream/4— accepts any module implementingparse_buffer/1with the same{events, remaining_buffer}contract as SSE parsing. Defaults to the existing SSE parser. Enables any provider with a non-SSE streaming format to plug in a custom parser.
-
Memory System: Persistent memory for agents with hybrid text + vector search, temporal decay, importance weighting, and flexible scoping.
Nous.Memory.Entry— memory entry struct with type (semantic/episodic/procedural), importance, evergreen flag, and scoping fields (agent_id, session_id, user_id, namespace)Nous.Memory.Store— storage behaviour with 8 callbacks (init, store, fetch, delete, update, search_text, search_vector, list)Nous.Memory.Store.ETS— zero-dep in-memory backend with Jaro-distance text searchNous.Memory.Store.SQLite— SQLite + FTS5 backend (requiresexqlite)Nous.Memory.Store.DuckDB— DuckDB + FTS + vector backend (requiresduckdbex)Nous.Memory.Store.Muninn— Tantivy BM25 text search backend (requiresmuninn)Nous.Memory.Store.Zvec— HNSW vector search backend (requireszvec)Nous.Memory.Store.Hybrid— combines Muninn + Zvec for maximum retrieval qualityNous.Memory.Scoring— pure functions for Reciprocal Rank Fusion, temporal decay, composite scoringNous.Memory.Search— hybrid search orchestrator (text + vector → RRF merge → decay → composite score)Nous.Memory.Embedding— embedding provider behaviour with pluggable implementationsNous.Memory.Embedding.Bumblebee— local on-device embeddings via Bumblebee + EXLA (Qwen 0.6B default)Nous.Memory.Embedding.OpenAI— OpenAI text-embedding-3-small providerNous.Memory.Embedding.Local— generic local endpoint (Ollama, vLLM, LMStudio)Nous.Memory.Tools— agent tools:remember,recall,forgetNous.Plugins.Memory— plugin with auto-injection of relevant memories, configurable search scope and injection strategy- 6 example scripts in
examples/memory/(basic ETS, Bumblebee, SQLite, DuckDB, Hybrid, cross-agent) - 62 new tests across 6 test files
-
Graceful degradation: No embedding provider = keyword-only search. No optional deps =
Store.ETSwith Jaro matching. The core memory system has zero additional dependencies.
- Anthropic and Gemini streaming: Added missing
Nous.StreamNormalizer.AnthropicandNous.StreamNormalizer.Geminimodules. These were referenced inProvider.default_stream_normalizer/0but never created, causing runtime crashes when streaming with Anthropic or Gemini providers.
Nous.StreamNormalizer.Anthropic— normalizes Anthropic SSE events (content_block_delta,message_delta,content_block_startfor tool use, thinking deltas, error events)Nous.StreamNormalizer.Gemini— normalizes Gemini SSE events (candidatesarray with text parts,functionCall,finishReasonmapping)- 42 tests for both new stream normalizers
-
Structured Output Mode: Agents return validated, typed data instead of raw strings. Inspired by instructor_ex.
Nous.OutputSchemacore module: JSON schema generation, provider settings dispatch, parsing and validationuse Nous.OutputSchemamacro with@llm_docattribute for schema-level LLM documentationvalidate_changeset/1optional callback for custom Ecto validation rules- Validation retry loop: failed outputs are sent back to the LLM with error details (
max_retriesoption) - System prompt augmentation with schema instructions
-
Output Type Variants:
- Ecto schema modules — full JSON schema + changeset validation
- Schemaless Ecto types (
%{name: :string, age: :integer}) — lightweight, no module needed - Raw JSON schema maps (string keys) — passed through as-is
{:regex, pattern}— regex-constrained output (vLLM/SGLang){:grammar, ebnf}— EBNF grammar-constrained output (vLLM){:choice, choices}— choice-constrained output (vLLM/SGLang)
-
Provider Modes: Controls how structured output is enforced per-provider
:auto(default) — picks best mode for the provider:json_schema—response_formatwith strict JSON schema (OpenAI, vLLM, SGLang, Gemini):tool_call— synthetic tool with tool_choice (Anthropic default):json—response_format: json_object(OpenAI-compatible):md_json— prompt-only enforcement with markdown fence + stop token (all providers)
-
Provider Passthrough:
response_format,guided_json,guided_regex,guided_grammar,guided_choice,json_schema,regex,generationConfignow passed through inbuild_request_params -
New Files:
lib/nous/output_schema.ex— core modulelib/nous/output_schema/validator.ex— behaviour definitionlib/nous/output_schema/use_macro.ex—use Nous.OutputSchemamacrodocs/guides/structured_output.md— comprehensive guideexamples/14_structured_output.exs— example script with 5 patternstest/nous/output_schema_test.exs— 42 unit teststest/nous/structured_output_integration_test.exs— 16 integration teststest/eval/agents/structured_output_test.exs— 3 LLM integration tests
Nous.Agentstruct gainsstructured_outputkeyword list field (mode, max_retries)Nous.Types.output_typeexpanded with schemaless, raw JSON schema, and guided mode tuplesNous.AgentRunnerinjects structured output settings, augments system prompt, handles validation retriesNous.Agents.BasicAgent.extract_output/2routes throughOutputSchema.parse_and_validate/2Nous.Agents.ReActAgent.extract_output/2validatesfinal_answeragainst output_type- Provider
build_request_params/3passes through structured output parameters
-
Sub-Agent plugin unified: Merged
ParallelSubAgentintoNous.Plugins.SubAgent- Single plugin now provides both
delegate_task(single) andspawn_agents(parallel) tools system_prompt/2callback injects orchestration guidance including available templates- Templates accept
%Nous.Agent{}structs (recommended) or config maps (legacy) - Parallel execution via
Task.Supervisor.async_stream_nolink - Configurable concurrency (
parallel_max_concurrency, default: 5) and timeout (parallel_timeout, default: 120s) - Graceful partial failure: crashed/timed-out sub-agents don't block others
- Single plugin now provides both
-
New Example:
examples/13_sub_agents.exs- Template-based sub-agents using
Nous.Agent.new/2structs - Parallel execution with inline model config
- Direct programmatic invocation bypassing the LLM
- Template-based sub-agents using
-
Plugin System: Composable agent extensions via
Nous.Pluginbehaviour- Callbacks:
init/2,tools/2,system_prompt/2,before_request/3,after_response/3 - Add
plugins: [MyPlugin]to any agent for cross-cutting concerns - AgentRunner iterates plugins at each stage of the execution loop
- Callbacks:
-
Human-in-the-Loop (HITL): Approval workflows for sensitive tool calls
requires_approval: trueonNous.Toolstructapproval_handleronNous.Agent.Contextfor approve/edit/reject decisionsNous.Plugins.HumanInTheLoopfor per-tool configuration via deps
-
Sub-Agent System: Enable agents to delegate tasks to specialized child agents
Nous.Plugins.SubAgentprovidesdelegate_tasktool- Pre-configured agent templates via
deps[:sub_agent_templates] - Isolated context per sub-agent with shared deps support
-
Conversation Summarization: Automatic context window management
Nous.Plugins.Summarizationmonitors token usage against configurable threshold- LLM-powered summarization with safe split points (never separates tool_call/tool_result pairs)
- Error-resilient: keeps all messages if summarization fails
-
State Persistence: Save and restore agent conversation state
Nous.Agent.Context.serialize/1anddeserialize/1for JSON-safe round-tripsNous.Persistencebehaviour withsave/load/delete/listcallbacksNous.Persistence.ETSreference implementation- Auto-save hooks on
Nous.AgentServer
-
Enhanced Supervision: Production lifecycle management for agents
Nous.AgentRegistryfor session-based process lookup via RegistryNous.AgentDynamicSupervisorfor on-demand agent creation/destruction- Configurable inactivity timeout on
AgentServer(default: 5 minutes) - Added to application supervision tree
-
Dangling Tool Call Recovery: Resilient session resumption
Nous.Agent.Context.patch_dangling_tool_calls/1injects synthetic results for interrupted tool calls- Called automatically when continuing from an existing context
-
PubSub Abstraction Layer: Unified
Nous.PubSubmodule for all PubSub usageNous.PubSubwraps Phoenix.PubSub with graceful no-op fallback when unavailable- Application-level configuration via
config :nous, pubsub: MyApp.PubSub - Topic builders:
agent_topic/1,research_topic/1,approval_topic/1 Nous.Agent.Contextgainspubsubandpubsub_topicfields (runtime-only, never serialized)Nous.Agent.Callbacks.execute/3now broadcasts via PubSub as a third channel alongside callbacks andnotify_pidAgentServerrefactored to useNous.PubSub— removes ad-hocsetup_pubsub_functions/0andsubscribe_fn/broadcast_fnfrom state- Research Coordinator broadcasts progress via PubSub when
:session_idis provided - SubAgent plugin propagates parent's PubSub context to child agents
-
Async HITL Approval via PubSub:
Nous.PubSub.Approvalmodulehandler/1builds an approval handler compatible withNous.Plugins.HumanInTheLoop- Broadcasts
{:approval_required, info}and blocks viareceivefor response respond/4sends approval decisions from external processes (e.g., LiveView)- Configurable timeout with
:rejectas default on expiry - Enables async approval workflows without synchronous I/O
-
Deep Research Agent: Autonomous multi-step research with citations
Nous.Research.run/2public API with HITL checkpoints between iterations- Five-phase loop: plan → search → synthesize → evaluate → report
Nous.Research.Plannerdecomposes queries into searchable sub-questionsNous.Research.Searcherruns parallel search agents per sub-questionNous.Research.Synthesizerfor deduplication, contradiction detection, gap analysisNous.Research.Reportergenerates markdown reports with inline citations- Progress broadcasting via callbacks,
notify_pid, and PubSub
-
New Research Tools:
Nous.Tools.WebFetch— URL content extraction with Floki HTML parsingNous.Tools.Summarize— LLM-powered text summarization focused on research queriesNous.Tools.SearchScrape— Parallel fetch + summarize for multiple URLsNous.Tools.TavilySearch— Tavily AI search API integrationNous.Tools.ResearchNotes— Structured finding/gap/contradiction tracking via ContextUpdate
-
New Dependencies:
floki ~> 0.36(optional, for HTML content extraction)phoenix_pubsub ~> 2.1(test-only, for PubSub integration tests)
Nous.Agentstruct now acceptsplugins: [module()]optionNous.Toolstruct now acceptsrequires_approval: boolean()optionNous.Agent.Contextnow includesapproval_handler,pubsub, andpubsub_topicfieldsNous.AgentServersupports optional:nameregistration,:persistencebackend, and usesNous.PubSub(removed ad-hocsetup_pubsub_functions/0)Nous.AgentServer:pubsuboption now defaults toNous.PubSub.configured_pubsub()instead ofMyApp.PubSubNous.AgentRunneraccepts:pubsuband:pubsub_topicoptions when building context- Application supervision tree includes AgentRegistry and AgentDynamicSupervisor
-
Evaluation Framework: Production-grade testing and benchmarking for AI agents
Nous.Evalmodule for defining and running test suitesNous.Eval.Suitefor test suite management with YAML supportNous.Eval.TestCasefor individual test case definitionsNous.Eval.Runnerfor sequential and parallel test executionNous.Eval.Metricsfor collecting latency, token usage, and cost metricsNous.Eval.Reporterfor console and JSON result reporting- A/B testing support with
Nous.Eval.run_ab/2
-
Six Built-in Evaluators:
:exact_match- Strict string equality matching:fuzzy_match- Jaro-Winkler similarity with configurable thresholds:contains- Substring and regex pattern matching:tool_usage- Tool call verification with argument validation:schema- Ecto schema validation for structured outputs:llm_judge- LLM-based quality assessment with custom rubrics
-
Optimization Engine: Automated parameter tuning for agents
Nous.Eval.Optimizerwith three strategies: grid search, random search, Bayesian optimization- Support for float, integer, choice, and boolean parameter types
- Early stopping on threshold achievement
- Detailed trial history and best configuration reporting
-
New Mix Tasks:
mix nous.eval- Run evaluation suites with filtering, parallelism, and multiple output formatsmix nous.optimize- Parameter optimization with configurable strategies and metrics
-
New Dependency:
yaml_elixir ~> 2.9for YAML test suite parsing
- New comprehensive evaluation framework guide (
docs/guides/evaluation.md) - Five new example scripts in
examples/eval/:01_basic_evaluation.exs- Simple test execution02_yaml_suite.exs- Loading and running YAML suites03_optimization.exs- Parameter optimization workflows04_custom_evaluator.exs- Implementing custom evaluators05_ab_testing.exs- A/B testing configurations
- Fixed
Usagestruct not implementing Access behaviour for telemetry metrics - Fixed
Task.shutdown/2nil return case inAgentServercancellation - Fixed tool call field access for OpenAI-compatible APIs (string vs atom keys)
- Vision/multimodal test suite with image fixtures (
test/nous/vision_test.exs) - ContentPart test suite for image conversion utilities (
test/nous/content_part_test.exs) - Multimodal message examples in conversation demo (
examples/04_conversation.exs)
- Updated docs to link examples to GitHub source files
- Improved sidebar grouping in hexdocs
-
Context Management: New
Nous.Agent.Contextstruct for immutable conversation state, message history, and dependency injection. Supports context continuation between runs:{:ok, result1} = Nous.run(agent, "My name is Alice") {:ok, result2} = Nous.run(agent, "What's my name?", context: result1.context)
-
Agent Behaviour: New
Nous.Agent.Behaviourfor implementing custom agents with lifecycle callbacks (init_context/2,build_messages/2,process_response/3,extract_output/2). -
Dual Callback System: New
Nous.Agent.Callbackssupporting both map-based callbacks and process messages:# Map callbacks Nous.run(agent, "Hello", callbacks: %{ on_llm_new_delta: fn _event, delta -> IO.write(delta) end }) # Process messages (for LiveView) Nous.run(agent, "Hello", notify_pid: self())
-
Module-Based Tools: New
Nous.Tool.Behaviourfor defining tools as modules withmetadata/0andexecute/2callbacks. UseNous.Tool.from_module/2to create tools from modules. -
Tool Context Updates: New
Nous.Tool.ContextUpdatestruct allowing tools to modify context state:def my_tool(ctx, args) do {:ok, result, ContextUpdate.new() |> ContextUpdate.set(:key, value)} end
-
Tool Testing Helpers: New
Nous.Tool.Testingmodule withmock_tool/2,spy_tool/1, andtest_context/1for testing tool interactions. -
Tool Validation: New
Nous.Tool.Validatorfor JSON Schema validation of tool arguments. -
Prompt Templates: New
Nous.PromptTemplatefor EEx-based prompt templates with variable substitution. -
Built-in Agent Implementations:
Nous.Agents.BasicAgent(default) andNous.Agents.ReActAgent(reasoning with planning tools). -
Structured Errors: New
Nous.Errorsmodule withMaxIterationsReached,ToolExecutionError, andExecutionCancellederror types. -
Enhanced Telemetry: New events for iterations (
:iteration), tool timeouts (:tool_timeout), and context updates (:context_update).
-
Result Structure:
Nous.run/3now returns%{output: _, context: _, usage: _}instead of just output string. -
Tool Function Signature: Tools now receive
(ctx, args)instead of(args). The context provides access toctx.depsfor dependency injection. -
Examples Modernized: Reduced from ~95 files to 21 files. Flattened directory structure from 4 levels to 2 levels. All examples updated to v0.8.0 API.
-
Removed deprecated provider modules:
Nous.Providers.Gemini,Nous.Providers.Mistral,Nous.Providers.VLLM,Nous.Providers.SGLang. -
Removed built-in tools:
Nous.Tools.BraveSearch,Nous.Tools.DateTimeTools,Nous.Tools.StringTools,Nous.Tools.TodoTools. These can be implemented as custom tools. -
Removed
Nous.RunContext(replaced byNous.Agent.Context). -
Removed
Nous.PromEx.Plugin(users can implement custom Prometheus metrics using telemetry events).
-
Stream completion events: The
[DONE]SSE event now properly emits a{:finish, "stop"}event instead of being silently discarded. This ensures stream consumers always receive a completion signal. -
Documentation links: Fixed broken links in hexdocs documentation. Relative links to
.exsexample files now use absolute GitHub URLs so they work correctly on hexdocs.pm.
-
Make all provider dependencies optional:
openai_ex,anthropix, andgemini_exare now truly optional dependencies. Users only need to install the dependencies for the providers they use. -
Runtime dependency checks: Provider modules now check for dependency availability at runtime instead of compile-time, allowing the library to compile without any provider-specific dependencies.
-
OpenAI message format: Messages are now returned as plain maps with string keys (
%{"role" => "user", "content" => "Hi"}) instead ofOpenaiEx.ChatMessagestructs. This removes the compile-time dependency onopenai_exfor message formatting.
-
Fixed "anthropix dependency not available" errors that occurred when using the library in applications without
anthropixinstalled. -
Fixed compile-time errors that occurred when
openai_exwas not present in the consuming application.
Initial public release with multi-provider LLM support:
- OpenAI-compatible providers (OpenAI, Groq, OpenRouter, Ollama, LM Studio, vLLM)
- Native Anthropic Claude support with extended thinking
- Google Gemini support
- Mistral AI support
- Tool/function calling
- Streaming support
- ReAct agent implementation