Gotchas
A don't-step-here list for LLMs and agents — every item maps to real spineagent code behavior.
给 LLM / agent 的「别踩」清单。每条都对应真实代码行为。
1) provider 的输出是 OpenAI ChatCompletion 形状(不是各家 native)
所有 provider 适配器都实现 corespine 的 LLMProvider 协议:chat(messages, *, tools=None) 返回
corespine.llm.provider.ChatCompletion,统一 OpenAI 形状——无论后端是 Anthropic / Gemini /
Cohere / Bedrock 还是 OpenAI 兼容端点。取结果固定这么写:
completion.choices[0].message.content # 文本(可能为 None)
completion.choices[0].message.tool_calls # tuple[ToolCall, ...] | None
completion.choices[0].message.tool_calls[0].function.name # 工具名
completion.choices[0].message.tool_calls[0].function.arguments # JSON 字符串(不是 dict!)
completion.choices[0].finish_reason # 'stop' / 'tool_calls' / 'length' / 'content_filter'
completion.usage.prompt_tokens / .completion_tokens / .total_tokens # usage 可能为 Nonefunction.arguments是 JSON 字符串,要json.loads(...)才是 dict(FunctionCallingAgent内部已这么做)。- 别假设拿到的是某家 native 响应对象——native → OpenAI 的转换在各适配器内部完成,对外只有这一种形状。
2) 离线默认是 MockProvider,它不会 function-calling
不传真实 provider 时,默认走 corespine.MockProvider:对同一对话恒定回
[mock:<hex>] <最后一条 user 文本>。它故意不伪造 tool_calls(离线不假装会推理)。后果:
LlmAgent(name, MockProvider())只做确定性回声,适合跑通管道 / 断言,不会真做规划或工具选择。FunctionCallingAgent(..., MockProvider(), tools=[...])因 mock 不回 tool_calls,会第一步就出文本, 绝不进工具循环。要演示 / 测试 function-calling 循环,注入一个会回tool_calls的 provider(真实 后端,或脚本化 fake——见 recipes §10)。- 离线想要确定性的「工具循环」,用
ToolUsingAgent+SyntaxToolPolicy(按<tool>: <arg>语法路由), 而不是FunctionCallingAgent。
3) 真实后端要装对应 extra(否则友好报错,而非裸 ModuleNotFoundError)
import spineagent 绝不拉任何网络 SDK。真实后端在构造适配器(且未注入 client=)时才延迟 import:
| 后端 | provider | 安装 |
|---|---|---|
| OpenAI / 一切 OpenAI 兼容端点 | OpenAICompatProvider | pip install "spineagent[openai]" |
| Anthropic | AnthropicProvider | pip install "spineagent[anthropic]" |
| Cohere | CohereProvider | pip install "spineagent[cohere]" |
| Google Gemini | GeminiProvider | pip install "spineagent[gemini]" |
| AWS Bedrock | BedrockConverseProvider | pip install "spineagent[bedrock]" |
| MCP 真实 SDK | mcp_clients.make("real") / load_mcp_sdk() | pip install "spineagent[mcp]" |
| A2A 真实 SDK | a2a_agents.make("real") / load_a2a_sdk() | pip install "spineagent[a2a]" |
| 全部 | — | pip install "spineagent[all]" |
缺对应 extra 去构造真实适配器,会得到指明「pip install spineagent[<extra>]」的友好 ImportError。
注意:[gemini] 装的是 google-genai,[a2a] 装的是 a2a-sdk(import 名 a2a)。
4) SeamError 表示「缝槽存在但真实实现未接入」
家族统一约定:某些缝的 real / llm 槽是占位,本包只提供缝 + 离线 stub:
tool_policies.make("llm")—— 直接抛corespine.errors.SeamError(无 SDK 可 import,留待接真 provider 解析 function-calling 后接入)。mcp_clients.make("real")/a2a_agents.make("real")—— 两阶段:未装对应 extra 时先抛ImportError(缺[mcp]/[a2a]);装了 extra 后才走到SeamError(SDK 在但适配器留待使用者 按官方 SDK 接入)。
经验法则:ImportError = 缺 extra(照提示 pip install spineagent[<extra>]);SeamError = 你选了
一个尚未接入的真实槽。两者都意味着:要么改用离线默认(offline),要么自己接入真实实现。
5) FunctionTool 与 Tool 是两种东西,别混用
Tool协议:run(arg: str) -> ToolResult。给ToolUsingAgent(离线语法路由)用。例:CalcTool、EchoTool、McpClientTool、AgentTool。FunctionTool(及@function_tool):schema()+invoke(args: dict) -> str。给FunctionCallingAgent(真 LLM function-calling)用。它不实现Tool.run。
把 @function_tool 装出来的对象丢进 ToolUsingAgent(或反过来把 CalcTool 丢进 FunctionCallingAgent)
会出错——它们走的是不同的工具协议。
6) $prev 只在 ToolUsingAgent 里、且首步替换为空串
ToolUsingAgent 在执行工具前把参数里的字面量 $prev 替换为上一步观测的输出。若首步就引用
$prev(尚无上一步),替换为空串——余下参数能否被工具处理由工具自身决定(如 CalcTool 对空串会抛
ValueError,异常照常上抛)。错误处理 / 重试不在本层(交编排层 / 调用方)。$prev 是 ToolUsingAgent
的语义,不适用于 FunctionCallingAgent(后者由模型自己串联多轮)。
7) max_steps 计的是工具调用次数,不是 LLM 轮数语义要分清
ToolUsingAgent(max_steps=N):N = 最多调用多少次工具;收尾决策本身不占预算。触顶强制收尾、绝不死循环。FunctionCallingAgent(max_steps=N):N = 最多 chat 轮数(每轮一次model.chat);触顶兜底返回"(reached max_steps without a final answer)"。两者都保证产出非空。
8) 编排默认 fail-fast;弹性容错要显式开 resilient
Coordinator.run_*(...) 默认 resilient=False——任一 agent 抛异常即冒泡。要「坏 agent 不炸整批」,
传 resilient=True:异常被归一为 error_to_dict(exc) 塞进该步 AgentResult.error(r.ok 为 False),
顺序 / 并行跑完其余 agent;流水线则在失败处停止(下游拿不到输入)。ChainAgent.step 内部走流水线
但不透传 resilient,始终 fail-fast。
9) trace 只吃元数据,塞正文会被拒
任何 step / 编排方法可选的 trace= 只接受元数据(agent 名、步序、计数、长度、耗时)。
corespine.InProcessPrivacyTraceSink 「构造即保证」:写入命中 FORBIDDEN_KEYS(content / text /
answer / value / prompt / completion …)的字段会当场抛 TraceError。所以别期望从 trace 里
读回任务或输出正文——那是设计上挡死的。
10) provider 默认 max_tokens 与默认 model 是真实的、要留意
各 provider 有真实默认值(来自源码),用前确认是否符合预期:
AnthropicProvider默认model="claude-opus-4-8"、max_tokens=4096。OpenAICompatProvider/BedrockConverseProvider:model必填(无通用默认);max_tokens默认 4096(Bedrock 经extra传)。CohereProvider默认model="command-r-plus";GeminiProvider默认model="gemini-2.5-flash"。- 需要 thinking / 流式 / 其它后端特有参数,经各 provider 的
extra=透传给底层 SDK 调用。reasoning / citations 等扩展本期不透出(统一规整为 OpenAI 形状时丢弃)。
11) 不要指望从 spineagent 拿 RAG
spineagent 不含任何 RAG 概念,也不在包层面依赖 ragspine。要做检索增强,在运行时把
ragspine(或任意检索能力)包成一个实现了 Tool(run(arg)->ToolResult)或 MCP server 的适配器,
插给某个 agent——方向只能 spineagent → ragspine,绝不写进 dependencies。