RunContextWrapper 核心属性速览
v0.14.8 可用属性
从「context 对象传了却对 LLM 无效」这个高频 bug 切入,系统拆解 OpenAI Agents SDK 的 Context 双轨设计:本地 RunContextWrapper 与 LLM-visible Context 的本质边界、ToolContext 的 5 个工具级元数据属性、多 Agent Handoff 下 Context 单例自动流转机制,附两个完整带注释代码示例(基础用法 + 客服多 Agent 流转),结尾给出 3 条立即可用的实践建议。
리서치 브리프
context 对象,满以为 Agent 能「记住」用户是谁,结果 LLM 给出的回答毫无个性化,完全不知道上下文里装了什么?RunContextWrapper:本地 Context 的载体RunContextWrapper[TContext] 是 SDK 对本地 Context 的泛型封装,把你的自定义业务对象在整个运行链路里透传1。wrapper.context:这里存的是你自定义的 Python 对象,比如用户 ID、数据库连接池、租户信息,任何你需要在代码里用的东西2。它不会被序列化、不会进 prompt,只在本地 Python 进程内流转。wrapper.usage特别有用——它聚合了单次运行里所有 Agent 消耗的 token 总量,省去你自己累加的麻烦。
from dataclasses import dataclass
from agents import Agent, Runner, function_tool
from agents.run_context import RunContextWrapper
# 1. 定义你的业务状态对象
@dataclass
class UserInfo:
name: str
uid: int
is_premium: bool = False
# 2. 工具函数接收 RunContextWrapper[UserInfo],获取本地 Context
@function_tool
def greet_user(wrapper: RunContextWrapper[UserInfo]) -> str:
"""向当前登录用户打招呼"""
user = wrapper.context # 拿到 UserInfo 实例
tier = "高级会员" if user.is_premium else "普通用户"
return f"你好,{user.name}!你当前是{tier}(UID: {user.uid})"
# 3. Agent 标注泛型类型,绑定 UserInfo
agent: Agent[UserInfo] = Agent(
name="PersonalAssistant",
instructions="你是一个个人助理,可以通过工具获取用户信息来提供服务。",
tools=[greet_user],
)
# 4. 运行时把 UserInfo 实例传给 context 参数
user_info = UserInfo(name="张三", uid=10086, is_premium=True)
result = Runner.run_sync(
starting_agent=agent,
input="请帮我打个招呼",
context=user_info, # 整次运行内所有工具都能访问
)
print(result.final_output)
# 示例输出:你好,张三!你当前是高级会员(UID: 10086)
# 5. 查看 token 消耗(wrapper.usage 聚合了全部 Agent)
# result.usage.total_tokens 可直接查看Agent[UserInfo] 是泛型标注,让类型检查器(mypy / pyright)在编译期捕获类型不匹配——比如你把 OrderInfo 传给一个期望 UserInfo 的 Agent,立刻报错1。greet_user 里的 wrapper.context.name 是 Python 本地读取,对 LLM 完全透明。如果你想让模型「知道」用户叫什么,必须把用户名写入 instructions 或通过工具返回值暴露出来。ToolContext:工具级元数据的精准入口ToolContext 是 RunContextWrapper 的子类,在工具执行和工具生命周期钩子里可用,额外暴露:| 属性 | 含义 |
|---|---|
tool_name | 当前工具名称(如 "search_web") |
tool_call_id | 此次调用的唯一 ID,可用于日志追踪 |
tool_arguments | LLM 传入的原始参数 JSON 字符串 |
tool_namespace | 工具所属命名空间 |
qualified_tool_name | 带命名空间修饰的完整工具名 |
tool_call_id 实现幂等控制1。Runner.run() 调用所有涉及的 Agent,共享同一个 Context 实例。wrapper.context,Agent B 接手后可以直接读到3:from dataclasses import dataclass, field
from typing import List
from agents import Agent, Runner, function_tool, handoff
from agents.run_context import RunContextWrapper
@dataclass
class CustomerServiceContext:
customer_id: str
visited_agents: List[str] = field(default_factory=list) # 追踪流转路径
resolved: bool = False
# 前台分流 Agent 工具:记录首次接触
@function_tool
def log_triage(wrapper: RunContextWrapper[CustomerServiceContext]) -> str:
"""记录分流信息"""
ctx = wrapper.context
ctx.visited_agents.append("triage") # 直接修改 Context,下游 Agent 能看到
return f"客户 {ctx.customer_id} 已记录,准备分流"
# 退款 Agent 工具:读取并更新 Context
@function_tool
def process_refund(wrapper: RunContextWrapper[CustomerServiceContext]) -> str:
"""处理退款"""
ctx = wrapper.context
ctx.visited_agents.append("refund") # 追加到路径链
ctx.resolved = True
return f"退款已处理,服务路径:{' → '.join(ctx.visited_agents)}"
# 定义退款专项 Agent
refund_agent: Agent[CustomerServiceContext] = Agent(
name="RefundAgent",
instructions="你专门处理退款请求,使用 process_refund 工具完成操作。",
tools=[process_refund],
)
# 前台分流 Agent,完成分流后 Handoff 给退款 Agent
triage_agent: Agent[CustomerServiceContext] = Agent(
name="TriageAgent",
instructions="你是前台客服,先记录分流信息,然后把退款请求转给退款专员。",
tools=[log_triage],
handoffs=[refund_agent], # 声明可交接的目标 Agent
)
# 执行:Context 在 TriageAgent → RefundAgent 间自动流转
ctx = CustomerServiceContext(customer_id="C-2025-001")
result = Runner.run_sync(
starting_agent=triage_agent,
input="我要申请退款",
context=ctx,
)
# RefundAgent 处理完后,ctx 里记录了完整路径
print(ctx.visited_agents) # ['triage', 'refund']
print(ctx.resolved) # Truecontext 对象透传给下一个 Agent3。Runner.run() 调用涉及的所有 Agent(包括通过 Handoff 链接的)必须使用相同的 TContext 类型。如果 TriageAgent 用 CustomerServiceContext,而 RefundAgent 用 OrderContext,运行时会类型错误。这是 SDK 的强制约束,不是可以绕过的限制1。 input_filter 与 on_handoffinput_filter3。from agents.handoffs import HandoffInputData
from agents import handoff
def slim_history_filter(data: HandoffInputData) -> HandoffInputData:
"""
只保留最近 3 条消息,避免退款 Agent 接收过多无关上下文。
data.run_context 里有 RunContextWrapper,可以读写 Context。
"""
recent_items = list(data.new_items)[-3:]
return data.clone(new_items=tuple(recent_items))
refund_handoff = handoff(
agent=refund_agent,
input_filter=slim_history_filter, # 过滤传入历史
on_handoff=lambda ctx, _: print( # 交接时触发回调
f"即将移交,当前路径:{ctx.context.visited_agents}"
),
)instructions(静态或动态函数生成)Runner.run(input=...) 传入的初始消息RunContextWrapper,而且可以在验证逻辑里嵌套调用子 Agent——直接把原 Context 传进去5:@input_guardrail
async def policy_check(
ctx: RunContextWrapper[CustomerServiceContext], # 拿到同一个 Context
agent: Agent,
input: str,
) -> GuardrailFunctionOutput:
# 嵌套调用验证 Agent,复用当前 Context
result = await Runner.run(
validation_agent,
input=input,
context=ctx.context, # 透传,避免重新构建
)
triggered = "违规" in result.final_output
return GuardrailFunctionOutput(
output_info=result.final_output,
tripwire_triggered=triggered,
)dict 意味着放弃类型检查。用 @dataclass 或 BaseModel 定义 Context 类,配合 Agent[YourContext] 泛型标注,mypy/pyright 会在 CI 里替你挡住类型不匹配的问题1。instructions 灵活,也更容易测试和维护6。wrapper.context.field 读写,干净且不消耗 token3。RunConfig.model 和 Agent.model 的优先级覆盖逻辑、如何接入非 OpenAI 模型(Anthropic、Gemini、本地 Ollama),以及 ModelSettings 里 temperature/top_p 的覆盖机制。
이 콘텐츠를 둘러싼 관점이나 맥락을 계속 보강해 보세요.