惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
O
OpenAI News
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
S
Schneier on Security
Latest news
Latest news
F
Full Disclosure
T
Tenable Blog
T
Troy Hunt's Blog
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
L
LangChain Blog
有赞技术团队
有赞技术团队
Project Zero
Project Zero
Cloudbric
Cloudbric
爱范儿
爱范儿
GbyAI
GbyAI
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
The Exploit Database - CXSecurity.com
S
Security @ Cisco Blogs
Hugging Face - Blog
Hugging Face - Blog
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
Last Week in AI
Last Week in AI
C
Cisco Blogs
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V2EX - 技术
V2EX - 技术
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Schneier on Security
Schneier on Security
T
Threat Research - Cisco Blogs
M
MIT News - Artificial intelligence
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
Kaspersky official blog
The Hacker News
The Hacker News
V
V2EX
F
Fortinet All Blogs
L
LINUX DO - 最新话题
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News | PayPal Newsroom
博客园 - 三生石上(FineUI控件)
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

LangChain Forum - Latest topics

Seeking help regarding the connection between Websocket and tool calls Tool invocation error with empty error message when using `InjectedState` + `Command` return in async tool How to use @langchain/react FileSystem middleware Using ChatSnowflake with agents Built llmsessioncontract on AgentMiddleware: runtime enforcement of tool-call protocols — feedback wanted DeltaChannelHistory not found in langgraph-api:3.12 Improving citation accuracy and reducing hallucinations in custom Parent-Child RAG pipeline (Gemma3:4B + FAISS+BM25 + Cross-encoder reranker) Built a live autonomous AI agent network using LangGraph-style economics — looking for feedback How are you validating LangChain agent output before it executes shell commands? Cross-site pattern pool for production agent failures — looking for 5 pilot teams (open spec, CC-BY-4.0) Metadata filter not filtering for alerts Using custom MCP servers with assistants How to use tool calling using ChatLlamaCpp and Gemma 4 E4B with create_agent? CLI - No Longer Sending traces to Langsmith Connecting the Slack integration fails with invalid_team_for_non_distributed_app The Docs says open router can be used with init_chat_model but throws an error Interested to contribute to langgraph postgre checkpointer for multiple adapter support Modal Inference Trouble understanding and editing experiment summary evaluators feedbacks SSL certificate error from httpx with LangGraph server [Feature Request] Wire allowed_msgpack_modules in langgraph.json Serving an agent with the LangGraph CLI dev command Proposal: implement delete_for_runs for SQLite checkpoint savers WikipediaLoader endup in JSONDecodeError Human-in-the-loop approval dashboard for LangGraph agents — open source, free to deploy Ombre — open source security and audit layer for LangChain apps Should interrupt() be split into two primitives — one for human input, one for s2s data fetching? Unable to delete runs from annotation queue First Bedrock call after idle is slow on TTFT (follow-ups in the same trace are fast) How are people handling data governance across agent handoffs in production? CLI: Read pdf is failing with DeepAgents CLI How can I submit a small fix for API token management in the community Jira toolkit? Feature Request: Driver abstraction for checkpoint-postgres: to build support for asyncpg and other adapters .Interested to contribute to langgraph (python) Feature Request: @task metadata Research: Friction Points in Agentic Commerce Transactions 3 agent integration patterns for claim verification (verify-then-act, decompose-and-score, multi-agent supervisor) How should I provide an agent to a LangGraph server? Parallel astream() on the same compiled graph leaks messages between streams How I added claim verification to a LangChain agent in 5 minutes (with code) Interested in contributing a DynamoDB checkpointer for LangGraph.js A set of speculative ideas on treating LLM agents as interpreters with limited working memory, using externalized structures for reliable long-term project maintenance. Where should I define the name and description for subagents? Add Qdrant-backed checkpoint saver and memory store (langgraph-checkpoint-qdrant) The output content has been corrupted Incorrect `reasoning_effort` options in the UI A Minimal Receipt + Validator Pattern for Tool-Calling Agents Feature Request: Simple cryptographic provenance for who authorized what in LangGraph multi-agent graphs Using LangGraph interrupt for multi-step wizards with branching — right tool or wrong abstraction? Portable departure and admission records for LangChain agents Socratic Tools: Intelligent Agent Enhancement for LangChain Built NORNR for spend governance in agent workflows LangGraph + PostgreSQL: Chat history and summarization best practice Using SQLRecordManager multi-agent systems debugging agent-to-agent Proposal: Add save_local and load_local to USearch VectorStore (Feature Parity with FAISS) How are you handling agent security in production? (Identity, permissions, kill switch) RFC: Human delegation provenance for LangGraph multi-agent chains Tiny LangGraph -> Assay evidence sample from tasks v2 tool isolation between Skills: Bug: Cannot save edited prompt back to Prompt Hub after opening trace run in Playground Add faiss-node-native as async alternative vector store for FAISS Deep Agents: Clarify Multimodal (Image) Context Management and Compression Discussion about why LangGraph JS ToolNode doesn’t inject ToolRuntime.state like Python does, and what the correct workaround or intended design pattern is. xat-langchain: signed Agent-Signature headers on every tool call Help with local RAG pipeline – poor retrieval quality, wrong page numbers Building an AI networking agent (LangChain vs LangGraph vs Deep Agents)? LOGIC.md — declarative reasoning contracts that compile to LangGraph StateGraph Distinguishing internal vs final streamed chunks in Supervisor multi-agent architecture Machine-Readable Permissions for Web-Interacting Agents How do ContextEditingMiddleware and SummarizationMiddleware interact when used together?Combining ContextEditingMiddleware + SummarizationMiddleware — execution order and behavior when both trigger? How we add runtime security to LangChain agents in production What does recursionLimit actually count in createAgent? (LangChain JS) How to register type in langgraph Are there any frontends for interacting with a LangGraph agent? Langchain.schema is not available while using in python code In-place model update on a compiled create_agent and per-subagent model update for deep agents - is this possible? Physical control surface for AI agents — feedback wanted Feature Request: Image Input support for ChatMistralAI Cache disable in Deepagent Unable to parse docstring from OpenAI schema Enable OpenTelemetry traces without sending data to LangSmith No cost displayed in LangSmith when using LiteLLM + LangGraph I had a few months Experience with across Agent deploys remotegraph calls - old/new dist runtime architecture Runs visiblty problem AI Agent Identity Management Framework How should I deploy a self-hosted multi-agent system? Free Self-hosted LangGraph Agent Server: how to separate API and queue workers using Docker New integration: langchain-w2a — LangChain tools for W2A-enabled websites When a LangChain run “looks right” but probably shouldn’t be trusted LiteLLM Router in LangChain: Missing Model Name and Cost in LangSmith Traces How to use subagent interrupt in streamEvent? BaseSandbox.write() fails if file already exists -- any way to overwrite? Gmail MCP Error -32603: Tool execution failed RuntimeError: Cannot patch execution_info before it has been set on LangGraph Cloud Fleet and Langmsith - No output in the Trace Inaccurate results from the SQL agent tutorial Handling Non-PDF File Attachments in LangChain HumanMessage [FEATURE-REQUEST] Ability to add builtin tools in Prompt Playground/Evaluations How are teams handling evals when agent pipelines span multiple LangSmith projects?
Multiple response formats when creating agents?
@pawel-tward · 2026-04-19 · via LangChain Forum - Latest topics

April 17, 2026, 9:32am 1

It seems like when creating agents using create_agent() it is only possible to give the agent a single response format.

For my specific project, I would like my agent to respond in different formats depending on the situation.
Currently I achieve this by using seperate create_agent() instances, but this doesn’t feel very satisfactory.

Is there some way I can have a single agent, that is capable of following multiple response formats?
Or any ideas to achieve a similar effect without multiple create_agent() instances?

hi @Bergholdt

I think you don’t need multiple agent instances. create_agent’s response_format accepts one strategy object, but that object can wrap many schemas. Three patterns:

  1. ToolStrategy(Union[A, B, …]) - model picks the right schema per run. This is the documented, intended solution. Each union arm becomes a separate artificial structured-output tool (see _iter_variants in langchain/agents/structured_output.py).
  2. JSON Schema oneOf - same mechanism at the schema level if you’re not using Pydantic.
  3. wrap_model_call middleware that overwrites request.response_format - if you (not the model) decide the format based on runtime context, state, or the latest message. ModelRequest.response_format is a writable field, so one agent can dispatch to N formats per invocation.

Caveat: only ToolStrategy expands unions. ProviderStrategy / AutoStrategy take a single schema, so to get provider-native validation you must pick one variant (option 3 is how).

1 Like

Hi @pawel-twardziak
Thanks for the reply!

It is indeed me that decides the format. I know the format I want beforehand.
So if I understand you right I could create a wrap_model_call middleware that directly changes the response_format of the agent?
So it does something like this?:
def wrap_model_call(request, handler):
request.response_format = insert_current_format_here
return handler(request)

yes - that exact pattern works, with one important caveat about ToolStrategy that the docs don’t spell out.

What the source code actually does after your middleware runs

After wrap_model_call fires, _get_bound_model re-reads request.response_format (langchain/agents/factory.py:1198) and rebuilds the bound model from it. So mutating request.response_format inside the handler is enough - no other plumbing is required.

Caveat: ToolStrategy swaps must be a subset of the upfront declaration

This is buried in the factory but it will bite you if you don’t know it. When the final (post-middleware) format is a ToolStrategy, the factory validates that every schema variant was declared when the agent was built:

# sources/langchain/libs/langchain_v1/langchain/agents/factory.py
# Middleware is allowed to change the response format
# to a subset of the original structured tools when using ToolStrategy,
# but not to add new structured tools that weren't declared upfront.
for tc in effective_response_format.schema_specs:
    if tc.name not in structured_output_tools:
        msg = (
            f"ToolStrategy specifies tool '{tc.name}' "
            "which wasn't declared in the original "
            "response format when creating the agent."
        )
        raise ValueError(msg)

Concretely:

  • If you built the agent with response_format=ToolStrategy(A) and the middleware swaps in ToolStrategy(B), you get ValueError - B wasn’t declared up front.
  • The fix: declare the superset as a Union at construction time, and let the middleware pick one variant per invocation.
  • This check does not apply to ProviderStrategy. If you swap to ProviderStrategy(X) where the model supports native structured output, the new schema is accepted without the upfront-declaration requirement.

The full pattern for a predetermined, per-invocation format

Since you said you already know which format you want at call time, pass it via context_schema - that’s the read-only per-invocation channel the middleware docs recommend, and it’s exactly what runtime.context is for (see docs/src/oss/langchain/context-engineering.mdx):

from dataclasses import dataclass
from typing import Callable, Union

from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.agents.structured_output import ToolStrategy


class ProductReview(BaseModel): ...
class CustomerComplaint(BaseModel): ...
class SupportTicket(BaseModel): ...

SCHEMAS = {
    "review": ProductReview,
    "complaint": CustomerComplaint,
    "ticket": SupportTicket,
}


@dataclass
class Context:
    response_format_key: str  # predetermined by the caller


@wrap_model_call
def select_format(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    key = request.runtime.context.response_format_key
    request.response_format = ToolStrategy(SCHEMAS[key])  # subset of the Union
    return handler(request)


agent = create_agent(
    model="gpt-5",
    tools=tools,
    # Declare the superset upfront so the subset swap above is always valid.
    response_format=ToolStrategy(Union[ProductReview, CustomerComplaint, SupportTicket]),
    middleware=[select_format],
    context_schema=Context,
)

agent.invoke(
    {"messages": [...]},
    context=Context(response_format_key="complaint"),
)

Why this shape:

  1. The Union on create_agent pre-registers every tool that any invocation might need - that clears the factory’s subset check.
  2. context_schema is the right carrier for “the caller already decided”: it’s read-only, per-invocation, and doesn’t pollute state.
  3. wrap_model_call runs once per model step, so retries inside the same invoke keep the format you picked.

If instead you want to skip the Union gymnastics and your model has native structured output, swap to ProviderStrategy(SCHEMAS[key]) in the middleware - no upfront declaration needed, though you lose the Union fallback for providers without native support.

Sources

  • Source (git repo): langchain/libs/langchain_v1/langchain/agents/factory.py
    lines ~1196–1255 - response_format re-read after middleware, and the ToolStrategy subset validation.
  • Source (git repo): langchain/libs/langchain_v1/langchain/agents/middleware/types.py
    • ModelRequest.response_format is a writable dataclass field.
  • Docs: Overview - Docs by LangChain - wrap_model_call
    signature, editing the request, custom context_schema.
  • Docs: Context engineering in agents - Docs by LangChain -
    context_schema + runtime.context as the per-invocation channel.

1 Like

Wow thank you for the detailed response and guidance!
It’s much appreciated.

no problem - let me know if it works for you

1 Like

Worked like a charm! :smiley:

1 Like

Hmm, actually I am not certain it is working.
I am experiencing some issues.

I will provide snippets of my code this time for context.

# Response formats:
class AgentDecision(BaseModel): ...
class AgentJobSolution(BaseModel): ...
class AgentVote(BaseModel): ...

RESPONSE_FORMATS = {
    "decision": AgentDecision,
    "job_solve": AgentJobSolution,
    "vote": AgentVote,
}

@dataclass
class ResponseFormatContext:
    response_format_key: Literal["decision", "job_solve", "vote"]


class EnergyMiddleware(AgentMiddleware):
    ...
    def wrap_model_call(self, request, handler):
        key = request.runtime.context.response_format_key
        request.response_format = ToolStrategy(RESPONSE_FORMATS[key])
        return handler(request)


class EnergyAgent:
    ...
        self._agent = create_agent(
            model=model,
            tools=tools,
            middleware=[EnergyMiddleware(model_size=model_size)],
            response_format=ToolStrategy(Union[AgentDecision, AgentJobSolution, AgentVote]),
            context_schema=ResponseFormatContext
        )

That is the setup.
And then I am calling the agent down the road passing state info and setting the reponse_format_key accordingly. An example:

        # Invoke agent to make a decision
        response = self._agent.invoke(
            {"messages": decision_messages, 
            "energy": self.energy, 
            "max_energy": self.max_energy,
            "phase_log": [],
            },
            context=ResponseFormatContext(response_format_key="decision"),
        )

What I am noticing is that the agents don’t seem to have one specific response format as if I had called an agent that was just initialized with a single response format when using this approach.
I am having problems where the agent is returning one of the other response formats.
For instance in a call from the invoke example above, the agent called the AgentDecision ToolStrategy instead and returned that format.

When inspecting the EnergyMiddleware.wrap_model_call trace in LangSmith it also seems like the response format is not changed from the Union:

image

I’m not sure if I am missing something?

hi @Bergholdt

alright, lemme follow up on this - thanks for reporting the issues! :slight_smile:

1 Like

would it be possible for you to share the langsmith thread with me?

in the meantime

instead of

@wrap_model_call
def select_format(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    key = request.runtime.context.response_format_key
    request.response_format = ToolStrategy(SCHEMAS[key])  # subset of the Union
    return handler(request)

try this

  class EnergyMiddleware(AgentMiddleware):
      def wrap_model_call(self, request, handler):
          key = request.runtime.context.response_format_key
          return handler(
              request.override(response_format=ToolStrategy(RESPONSE_FORMATS[key]))
          )

Two changes: .override(…) returns a new ModelRequest, and you must forward that new request to handler. If you mutate in place and forward the original, the factory may still see the Union because of dataclass aliasing plus the deprecation path.

Also worth double-checking: that every invoke() passes context=ResponseFormatContext(response_format_key=“…”), and that the key actually exists in RESPONSE_FORMATS. Full known-good snippet is in the doc.