PAYATHON 2026

如何调整 langchain 中 structured chat agent 的输出格式?

排障赵师傅

固定格式应由应用代码或回调处理,Observation 直接使用工具返回值。只改 prompt,无法保证 verbose=True 的控制台日志按指定格式显示;把 stop_sequence 设为 False 也解决不了这个问题。

所有标签都可以按固定格式显示,但模型没有输出的 Thought 无法补回。这时应标注“未提供”,避免编造推理内容。

为什么缺少 Observation 标签

模型输出、工具结果和控制台日志分别由不同部分控制:

内容来源如何控制
模型生成的 action、回答文本模型prompt 和 output parser
SQL 查询结果实际执行的工具工具实现和数据库查询
控制台标签、换行、颜色日志回调callback handler

默认 StdOutCallbackHandler 会打印工具返回值,只有收到 observation_prefix 时才会打印对应前缀。因此,原始数据直接接在 action 后面,符合默认回调的行为。缺少标签本身不能证明查询没有执行。源码:StdOutCallbackHandler

传回模型的历史记录与控制台日志也有区别。create_structured_chat_agent 使用的 format_log_to_str 会将已执行的 action 和 observation 整理进 agent_scratchpad,其中包含 Observation: 和 Thought: 前缀。源码:format_log_to_str

Question 通常已作为输入传入,不一定会再次出现在模型输出中。Thought 也不是每次 action 都有的独立字段,不能仅凭日志中没有它,就认定内容被截断。

保留 agent 所需的 JSON 协议

Structured chat agent 使用包含 action、action_input 的 JSON 表达工具调用,例如:

{
  "action": "sql_db_query",
  "action_input": {
    "query": "SELECT COUNT(*) FROM orders"
  }
}

最终回答也使用这套协议:

{
  "action": "Final Answer",
  "action_input": "最终回答内容"
}

sql_db_query 必须替换为实际注册的工具名称。不要为了调整控制台排版,把 agent 的输出协议直接改成纯文本 Action Input:、Final Answer:,否则可能导致解析失败。展示时再把 JSON 字段转换成所需标签即可。源码:JSONAgentOutputParser

执行完成后,统一排版输出

要保存格式稳定的执行记录,可以启用 return_intermediate_steps=True。返回结果中的每一步都包含 AgentAction 和对应的 observation,可直接用于展示。源码:AgentExecutor

将以下代码接在已有的 llm、tools、prompt 初始化代码之后。示例使用 langchain_classic 导入路径;如果项目仍使用提供这些接口的旧版 langchain,请按实际安装版本调整导入路径。

import json
import re

from langchain_classic.agents import (
    AgentExecutor,
    create_structured_chat_agent,
)

agent = create_structured_chat_agent(
    llm=llm,
    tools=tools,
    prompt=prompt,
    stop_sequence=True,
)

executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=False,
    return_intermediate_steps=True,
)


def extract_visible_thought(log):
    # 只提取模型确实输出的 Thought 文本。
    match = re.search(
        r"(?:^|\n)Thought:\s*(.*?)(?=\nAction:|\Z)",
        log,
        flags=re.DOTALL,
    )
    if match and match.group(1).strip():
        return match.group(1).strip()
    return "(未提供可见的 Thought 文本)"


question = "orders 表共有多少条记录?"
result = executor.invoke({"input": question})

print(f"Question: {question}")

for action, observation in result["intermediate_steps"]:
    print(f"Thought: {extract_visible_thought(action.log)}")
    print(f"Action: {action.tool}")
    print("Action Input:")
    print(json.dumps(
        action.tool_input,
        ensure_ascii=False,
        indent=2,
        default=str,
    ))
    print("Observation:")
    print(observation)

print("Thought: (最终阶段的 Thought 未单独返回)")
print("Final Answer:")
print(result["output"])

代码直接打印每一步保存的工具返回值,无需让模型重新生成 Observation。执行查询工具时,展示的是该次调用返回的内容;执行 schema 查询或 SQL 检查工具时,展示的是相应工具的结果;发生错误时,则展示错误信息。

extract_visible_thought 只按 Thought: 文本约定提取内容,无法适配所有输出形式。模型只返回 JSON 时,显示“未提供”是正常的。intermediate_steps 也不单独包含最终回答阶段的 Thought,因此代码为这一阶段保留了占位说明。

需要实时查看时,自定义回调

要在实时日志中补上 Observation:,可以继承默认 stdout handler,修改工具执行结束时的打印方式:

from langchain_core.callbacks import StdOutCallbackHandler


class ObservationPrinter(StdOutCallbackHandler):
    def on_tool_end(self, output, **kwargs):
        print("\nObservation:", flush=True)
        print(output, flush=True)


# executor 使用上面创建的实例,verbose=False。
# 通过调用时的 config 传入,回调可传递给内部工具。
result = executor.invoke(
    {"input": question},
    config={"callbacks": [ObservationPrinter()]},
)

这个回调会补齐 observation 标签,action 和结束日志仍按默认 handler 的方式打印。如果需要实时统一全部标签,可以继续实现 on_agent_action 和 on_agent_finish,分别读取 action 的工具字段和 finish 的返回值。

使用自定义回调时,应关闭相关对象的默认 verbose 输出,避免两套日志重复打印。

stop_sequence 和数据完整性需要分开检查

模型支持 stop sequence 时,可以保留 stop_sequence=True。它会在模型生成到 observation 边界时停止生成,由执行器执行工具,再将真实结果传给后续模型调用。它不会自动给控制台添加标签,也不能保证最终回答完全正确。具体停止字符串以所安装版本的实现为准。源码:create_structured_chat_agent

排查时还需注意:

  • 示例日志调用的是 tavily_search_results_json,返回的是搜索结果。排查 SQL 场景时,应核对实际的 action.tool 和 action.tool_input。
  • 工具返回值可能已受到 SQL 的 LIMIT、工具实现或结果字符串长度设置的限制。重新排版无法恢复上游省略的数据。
  • 如果 action.log 本身不完整,再检查模型响应长度限制、停止条件和终端显示。仅凭现有片段,无法确定是哪一层发生了截断。
  • Final Answer 仍是 agent 返回的内容。核对具体数据时,应以查询工具返回的 observation 为依据。

备注:内容仅供参考。