运行时
运行时决定智能体「内层循环」如何执行——也就是每一轮怎样调用模型、解析意图、调用工具。默认运行时基于 Google ADK 的内置执行流程,开箱即用。你也可以切换到其他执行后端,或在执行流程上插入统一的处理逻辑(如鉴权、日志、重试),而无需改动智能体本身。
基本使用
默认即为 ADK 运行时,无需任何额外配置:
import asyncio
from veadk import Agent, Runner
agent = Agent(name="assistant") # 默认使用 ADK 运行时
print(asyncio.run(Runner(agent=agent).run("你好")))切换执行后端
通过 runtime 选择内层循环的执行后端:
agent = Agent(name="assistant", runtime="codex")| 取值 | 说明 |
|---|---|
adk(默认) | 使用 Google ADK 内置的执行流程,适用于绝大多数场景。 |
codex | 使用 OpenAI Codex SDK 驱动内层循环,在沙箱中执行命令与文件操作。 |
piagent | 使用本地 Pi coding agent 二进制(通过其 RPC 模式)驱动内层循环。二进制会在首次运行时自动下载,也可用 PIAGENT_BINARY 指定已有路径。 |
定位:codex / piagent 是「沙箱执行运行时」,不是 ADK 执行流程的等价替代品。
它们把整个内层循环交给外部 agent harness,因此 ADK 请求/响应处理器(planner、code_executor、example store、知识库注入)、逐次模型调用的回调与 span 不会在这层流程里执行。VeADK 会把 sub_agents 桥接成 transfer_to_agent 工具,但这些运行时仍更适合「模型需要在受控沙箱里跑命令、改文件」的任务,而不是所有 ADK 能力的透明后端替换。具体差异见下方支持矩阵。
使用 Codex runtime 前安装可选依赖:
pip install "veadk-python[codex]"什么时候该用 codex 运行时
上面的定位说明可以压成一条判断标准:codex 运行时只有一个真正的优势——模型可以写下一个文件、把它跑起来、读到报错、改掉、再跑一遍,整个循环发生在一个 OS 沙箱里,而你不需要事先把每一步都定义成工具。 除此之外的每一条轴上,它都比 runtime="adk" 更贵。
适合:
- 步骤无法事先枚举成工具——临时数据分析、日志排障、脏数据清洗、按需改代码。这类任务的下一步取决于上一步的输出,而不是取决于你写智能体时的设想。
- 工作对象是一个文件系统——中间产物留在 workspace 里,同一 Session 的下一轮仍然看得见(见 Workspace 生命周期)。
- 第一次做错是正常的,且重试很便宜——报错信息本身就是模型的下一份输入。在 ADK 运行时里,一次失败的工具调用只是一条错误字符串;在这里,它是一段可以被读、被诊断、被修好的 traceback。
不适合:
- 一次固定的工具调用 + 一段格式化回答。 这正是 ADK 运行时的主场:更快、更便宜,而且不受下面任何一条限制。
- 需要
output_schema、planner、code_executor或generate_content_config的智能体。 这些配置在非 adk 运行时下会直接报错,而不是降级——完整清单见支持矩阵。 - 对延迟敏感的链路。 每一次 invocation 都会重建一个临时
CODEX_HOME、拉起一个 Codex 子进程、开一个 ephemeral thread;这笔固定开销每回合都要付。 run_live。 非 adk 运行时没有 live/bidi 实现,见下文。
成本,如实说清楚:
- 每次 invocation 一个 Codex 子进程。 runtime 每回合都会重建
CODEX_HOME并进入AsyncCodex(...),thread 以ephemeral=True创建。 - 每回合都会把整段 ADK 会话历史重新序列化进 prompt。 历史被渲染成一个
<conversation_history>JSON 块,附在当前消息前面;codex 没有增量续接机制,所以 prompt token 随对话轮数增长。这也是include_contents="none"被拒绝(而不是被忽略)的原因。 - ADK 工具的结果会穿过模型上下文。 见下方 Workspace 是数据面——这是本运行时最容易踩的一个坑。
一句话版本:能把任务写成一串确定的工具调用,就用 runtime="adk";只有当你只能描述目标、路径必须试出来时,才用 runtime="codex"。
仓库里四个 codex 示例的分工:
| 示例 | 讲什么 |
|---|---|
codex_data_analysis | 本运行时是干什么用的。 Codex 写分析脚本 → 运行 → 在脏数据上撞到真实报错 → 自己改好 → 重跑 → 出报告。自迭代循环。 |
codex_ops_assistant | 本运行时是干什么用的。 ADK 工具把日志、指标与发布记录落进 workspace,Codex 写一次性脚本把三者关联起来、定位根因,全程在断网沙箱里。 |
codex_with_skill_and_mcp | 怎么接线。 本地 skill 和 MCP 工具在这个运行时下分别走哪条路。 |
codex_runtime_on_agentkit | 怎么部署。 把一个 runtime="codex" 智能体发布到火山引擎 AgentKit。 |
Codex 安全配置
Codex 默认使用独立 Session workspace、workspace_write 沙箱、禁用网络,并拒绝一切需要提权的操作:
from veadk import Agent
from veadk.runtime.codex import CodexRuntimeConfig
agent = Agent(
name="assistant",
runtime="codex",
codex_runtime_config=CodexRuntimeConfig(
sandbox="workspace_write",
approval_mode="deny_all", # 默认值;auto_review 等同于全自动批准,见下
network_access=True,
# 若需在已有工程内工作,显式指定目录;默认使用 Session 隔离目录。
workspace_root="/workspace/codex",
),
)approval_mode="auto_review" 不是「人工复核」,而是「全自动批准」。
Codex SDK 内置的审批处理器对每一个 requestApproval 通知都回答 accept,而 AsyncCodex 没有提供替换该处理器的接口。因此 auto_review 会自动批准每一次沙箱提权和文件修改,既不询问人工,也不经过 ADK。只有默认的 deny_all 才真正把 Codex 约束在沙箱内。在多租户或不可信输入场景中,请勿使用 auto_review。
关于沙箱与网络的组合,有一个容易误判的点:network_access 只会写入 Codex config.toml 的 [sandbox_workspace_write] 段,只有 workspace_write 沙箱会读取它。
sandbox | network_access 是否生效 |
|---|---|
workspace_write | 生效。 |
read_only | 不生效(设为 True 会记录一条警告)。 |
full_access | 不生效——danger-full-access 本身就意味着完全的网络与文件系统访问。 |
因此 CodexRuntimeConfig(sandbox="full_access", network_access=False) 会直接抛出 ValueError:这个组合读起来像「禁用网络」,实际却给了完全访问权限。需要 full_access 时必须显式写上 network_access=True 以确认风险。
full_access 和 reuse_workspace=True 会放宽不同调用之间的文件系统边界,只应在受信环境中开启。
Codex 执行的命令看不到凭证类环境变量。生成的 Codex 配置固定写入 [shell_environment_policy],从命令环境中排除以下变量:
| 排除模式 | 覆盖的变量 |
|---|---|
VEADK_CODEX_* | runtime 传给 Codex 的配置与本轮令牌。 |
*API_KEY* | 模型 API Key 等密钥,例如 MODEL_AGENT_API_KEY。 |
*SECRET* | 云账号 Secret Key 等凭证。 |
*TOKEN* | 各类访问令牌。 |
缺少该策略时,沙箱内执行 env 会打印出模型 API Key 与本轮令牌(已在 Codex CLI 0.159 上验证)。命令需要的其他凭证应通过 ADK 工具使用,而不是经由环境变量进入沙箱。
模型请求头同样不会进入沙箱。direct 传输下,model_extra_config.extra_headers 中名称为 Authorization、Proxy-Authorization、Cookie,或包含 key/token/secret/password/signature(不区分大小写)的请求头,不会写入 Codex 的配置文件:其值经 Codex 的 env_http_headers 由环境变量传入,沙箱内的 shell 看不到这些变量。其余请求头作为静态请求头发送。
安全基线组合
把上面几项拼起来,就是在不可信输入下跑 codex 的默认配方。四个设置各挡一个方向,缺一不可:
from google.adk.agents import RunConfig
from veadk import Agent, Runner
from veadk.runtime.codex import CodexRuntimeConfig
agent = Agent(
name="analyst",
runtime="codex",
tools=[load_orders, publish_report], # 唯一的进出通道
codex_runtime_config=CodexRuntimeConfig(
sandbox="workspace_write", # 只能写 workspace
network_access=False, # 沙箱内没有出网通道
approval_mode="deny_all", # 默认值;拒绝一切提权请求
),
)
await Runner(agent=agent).run(
"...",
run_config=RunConfig(max_llm_calls=40), # 单次 invocation 的成本上限
)它们之所以能组合成一套,是因为关掉网络之后,你自己挂上去的那几个 ADK 工具就是唯一的出站通道:模型可以在沙箱里对敏感数据任意计算、任意重试,但要把任何东西送出去,只能经过一个你审计过、有签名、有日志的函数。换句话说,工具列表就是你的数据出口策略——这条边界要成立,前提是每个工具自己也不提供任意外发能力。
配套的三条,不在这里重复展开:
approval_mode="auto_review"会拆掉这套配方的地基,理由见上方的红色警告。sandbox与network_access的组合语义见上表:只有workspace_write会读network_access。VEADK_CODEX_*环境变量的优先级高于这里的 Python 配置,见 Codex 环境变量。部署环境里一个VEADK_CODEX_SANDBOX=full_access就能推翻上面整段代码。
RunConfig(max_llm_calls=...) 不设时,VeADK 的 Runner 会用环境变量 MODEL_AGENT_MAX_LLM_CALLS(默认 100)兜底。在这个运行时下建议显式写一个更小的值:模型自己决定跑多少轮脚本,只有这个配额是硬上限。
Workspace 生命周期
默认情况下,workspace 由 app_name / user_id / session_id / agent 名四元组定位:同一 Session 的多次调用共享同一目录,回合结束后不会删除,因此上一轮写下的文件下一轮仍然可见。进程退出时整棵目录树被清理;进程存活期间,空闲超过 6 小时的 Session workspace 会在下一次创建 workspace 时被顺带回收,以限制长期运行的服务进程的磁盘增长。
工具不需要自己推导这个路径:不做任何配置,current_workspace() 就会返回当前调用它的这一轮的 workspace,见下方 Workspace 是数据面。
显式传入 workspace_root 时,这块目录属于你自己:runtime 既不会删除它,也不会对它执行上述空闲回收——它仍会在其下为每个 Session 建独立子目录,但这些子目录需要你自行清理。reuse_workspace=True 只有在同时设置了 workspace_root 时才有意义,它让所有 Session 直接共用该目录本身,而不再做 Session 隔离。
Workspace 是数据面
这是用好 codex 运行时最重要、也最容易被忽略的一条:ADK 工具的参数和返回值是控制面,workspace 才是数据面。
原因在工具的执行路径上。ADK/MCP 工具不由 Codex 自己调用,而是由 runtime 的 Responses shim 执行:shim 拿到工具返回值后 json.dumps 成字符串,作为一条 function_call_output 塞回模型的 input 数组。而且因为 Codex 每完成一次原生工具调用就会重建整个 input,shim 还必须在本回合后续的每一次后端请求里,把这对 function_call / function_call_output 重放一遍。于是:
一个返回 5 万行日志的工具,会把这 5 万行灌进模型上下文,并在同一回合里重复灌若干次。这不是慢一点的问题,是这一回合直接废掉。
正确的做法是让工具交出一个路径,而不是一份数据:把数据写进 workspace(Codex 的 cwd 就是它),只返回位置和少量元信息。数据落在磁盘上,模型用 shell 去读、去切片、去统计,上下文里只留下一行。
from pathlib import Path
from veadk.runtime.codex import current_workspace
def load_orders(day: str) -> dict:
"""把某一天的订单写成 CSV,放进你的工作目录。
只返回一张回执,不返回订单本身——请用你自己的代码去读返回路径上的 CSV。
"""
workspace = current_workspace() # 本轮的工作目录,或者 None
if workspace is None: # 不在 Codex 轮次里:返回模型能读懂的错误
return {"status": "error", "message": "no codex workspace on this call"}
rows = warehouse.query(day)
name = f"orders-{day}.csv"
write_csv(Path(workspace) / name, rows)
# 回执,不是数据:告诉模型文件在哪、有多大形状。
return {"path": name, "rows": len(rows), "columns": list(rows[0])}这里的 docstring 是真正干活的部分:模型读到的就是它,所以必须写清楚「返回的是回执」。 否则模型会直接让工具把数据交出来,你又回到了原点。
反方向同理:Codex 在 workspace 里写好报告之后,「发布」工具应该收下它写的那个文件的路径,由工具自己读盘再送往外部系统——而不是让模型把整篇报告当作工具参数复述一遍(那同样要过一遍上下文,而且内容可能在复述中走样)。
工具怎么知道 workspace 在哪:
current_workspace()——首选做法,也是多租户唯一可行的做法。from veadk.runtime.codex import current_workspace,返回当前调用该工具的这一轮 workspace 的绝对路径。它不需要任何配置,workspace_root与reuse_workspace都不设时同样有效。这个值是在每次工具调用前后绑定的,而不是从调用方的环境上下文里读,所以同一进程里并发的多个回合各自看到自己的目录。调用栈上没有 Codex 回合时它返回None,而不是抛异常——同一个工具对象也会被别的 runtime、被AgentTool、被单元测试执行——因此请判断None并返回你自己的{"status": "error", ...}让模型去处理,而不是抛异常,也不要退回到某个你自己选定的目录。- 固定路径——单租户,并且你想在跑完之后翻这个目录。 设置
workspace_root并配合reuse_workspace=True,此时 workspace 就是workspace_root本身,工具和CodexRuntimeConfig可以共用同一个常量,而且进程退出后目录仍在磁盘上。代价是所有 Session 共用它,见 Workspace 生命周期。
无论哪种做法,来自模型的路径都是不可信输入:打开之前请把它解析回 workspace,并拒绝一切越界的路径(..、绝对路径、指向外部的符号链接)。
Codex 执行参数
| 参数 | 默认值 | 说明 |
|---|---|---|
reasoning_effort | "medium" | 推理强度,可选 minimal/low/medium/high/xhigh。越高越慢、消耗 token 越多。 |
personality | "pragmatic" | Codex 自带的回复风格,可选 none/friendly/pragmatic。 |
model_transport | "auto" | Codex 如何访问模型:direct 让 Codex 直接调用支持 Responses API 的后端,并通过 runtime 为本轮启动的本地 MCP server 使用 Agent 的工具,工具循环由 Codex 驱动;shim 经由进程内 Responses→Chat 转换层,由转换层执行工具,适用于只支持 chat 的后端;auto 对火山方舟(*.volces.com)、BytePlus(*.bytepluses.com)与 api.openai.com 选 direct,其余选 shim;但 Agent 的 model_extra_config 含非空 extra_body 时,auto 仍选 shim,因为 direct 无法转发请求体。显式指定 direct 时 extra_body 不会被转发,并记录一条警告。 |
turn_timeout_seconds | 1800.0 | 单个 Codex 回合的时间上限(秒),到期后中断回合并抛出 TimeoutError;None 表示不限。direct 传输且 thread_mode="resume" 时,同一 Session 的调用在一把 Session 级锁上排队,不设上限的回合会同时阻塞该 Session 之后的所有调用,因此默认给出 30 分钟上限;其余模式同一 Session 的调用可以并发执行。 |
auto_compact_token_limit | None | 历史 token 超过该值时由 Codex 自行压缩(Codex 的 model_auto_compact_token_limit),仅 direct 传输生效;None 沿用 Codex 默认值。 |
thread_mode | "resume" | resume:每个 Session 绑定一个 Codex thread,thread 的 rollout 随 Session 一起保存(短期记忆使用数据库时存进同一个库),下一轮在任意实例上恢复该 thread,Codex 保有自己的完整历史,不再注入对话记录;Agent 的 instruction 变化时会新开 thread 并用对话记录引导。ephemeral:每次调用新开 thread。仅 direct 传输生效,shim 始终为 ephemeral。 |
max_tool_iterations | 32 | 整个 Codex turn 可执行的 ADK/MCP 工具调用次数上限(1–256)。两种传输都在执行前扣减配额,并行调用各计一次;不包含 Codex 原生工具。shim 在剩余配额不足时拒绝整个并行批次,direct 逐个拒绝超额调用。超限向调用方抛出 CodexToolIterationLimitError。 |
tool_timeout_seconds | 120.0 | 单个 ADK/MCP 工具调用的超时秒数;设为 None 表示不超时。 |
reuse_workspace | False | 仅在同时设置了 workspace_root 时生效,而且只值得在单租户场景下开启,见上方 Workspace 生命周期。否则两个字段都别设,让工具调用 current_workspace(),见 Workspace 是数据面。 |
行为变更:max_tool_iterations 的含义已改变,默认值从 8 提高到 32。
它现在约束的是整个 Codex turn,而不再是单次后端请求。Codex 每完成一次原生工具调用就会发起一次新的后端请求,所以旧的「每请求」计数实际允许的执行次数是「请求轮数 × budget」。默认值同时调高,是为了让「先跑若干轮原生工具、之后才调用 ADK 工具」的回合不被提前截断。如果你此前靠 8 这个值来兜底控制成本,请重新评估——现在更合适的成本上限是 RunConfig(max_llm_calls=...)(见下)。
指令是怎么下发给 Codex 的
Codex 自带一份约 20KB、针对自身工具链(apply_patch、update_plan、shell、AGENTS.md)调优过的系统提示词。VeADK 不会通过 base_instructions 覆盖它——Codex 在该字段被设置时会整体替换内置模板,这些指导会被全部删掉(Codex 官方文档也强烈不建议这么做)。
因此,Agent 的身份块与 instruction 走的是 Codex 原生的 developer_instructions 通道:Codex 把它渲染成一条独立的 developer 消息,与 AGENTS.md、skills、环境上下文并列,是纯追加而非替换。
副作用是 personality 现在真的会生效:它渲染在 Codex 内置的系统提示词模板里,而以前只要设置了 base_instructions,整个模板连同 personality 一起被替换掉,该字段形同虚设。
同一条通道上还会追加两条更正——Codex 被保留下来的系统提示词描述的工具链,这座桥并不能完整提供。runtime 会在每一轮的 developer instructions 后面追加一段简短的工具可用性说明:
- 本次运行没有
apply_patch:shim 只转发function类型的工具,而 Codex 的文件编辑工具不是,所以创建和修改文件要用exec_command(例如cat > file <<'EOF'heredoc)。 - (仅
shim传输)request_user_input确实被通告了,但没有人能回答它:一次 ADK 调用没有交互通道,调用它只会让这一轮什么都没做就结束。说明里会要求模型用手上已有的信息作判断,并在最终回复里讲清楚缺了什么。
direct 传输在 thread 配置里直接移除了 request_user_input(以及 web_search、多 agent 等用不到的工具),因此说明里只保留第一条。这些你都不需要再在自己 Agent 的 instruction 里手写一遍。
Codex 环境变量
以下六个环境变量覆盖 Python 中的 CodexRuntimeConfig,而不是作为它的默认值。也就是说,即使代码里写死了 sandbox="workspace_write",部署环境里的 VEADK_CODEX_SANDBOX=full_access 依然会生效。这一优先级与大多数配置项相反,在容器/函数计算等由平台注入环境变量的场景中尤其需要注意。
| 环境变量 | 覆盖的配置项 | 说明 |
|---|---|---|
VEADK_CODEX_SANDBOX | sandbox | 取值同 sandbox 字段。 |
VEADK_CODEX_APPROVAL_MODE | approval_mode | 取值同 approval_mode 字段。 |
VEADK_CODEX_WORKSPACE_ROOT | workspace_root | 工作目录绝对路径。 |
VEADK_CODEX_NETWORK_ACCESS | network_access | 1/true/yes/on 视为开启,其余视为关闭。 |
VEADK_CODEX_MODEL_TRANSPORT | model_transport | auto/direct/shim。 |
VEADK_CODEX_THREAD_MODE | thread_mode | resume/ephemeral。 |
另有四个用于 Responses→Chat 转换层(shim)的环境变量。它们不参与上面的覆盖规则,只是 shim 自身的调参:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
CODEX_SHIM_NUM_RETRIES | 2 | 后端请求遇到 429/5xx/超时等瞬时错误时的重试次数。 |
CODEX_SHIM_TIMEOUT | 0(不超时) | 单次后端请求的超时秒数。 |
CODEX_SHIM_START_TIMEOUT | 10 | 等待 shim 的本地 HTTP 服务起来的秒数;超时即判定启动失败并明确报错,而不是让 Codex 一直连不上。 |
CODEX_SHIM_CACHE_MAX | 8 | 进程内缓存的 shim 实例上限(按后端地址+凭证区分),用于限制多租户进程里的服务与端口数量。 |
另有三个 thread 存储的容量参数,同样不参与覆盖规则,含义见 限制:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
VEADK_CODEX_MAX_ROLLOUT_BYTES | 33554432(32 MiB) | 单个 thread rollout 的大小上限(字节),超过后不再保存。 |
VEADK_CODEX_MEMORY_STORE_MAX_BYTES | 268435456(256 MiB) | 短期记忆使用 local 后端时,进程内 thread 存储的总字节上限,超出后按最近最少使用淘汰。 |
VEADK_CODEX_MEMORY_STORE_MAX_RECORDS | 10000 | 同上,进程内 thread 存储的记录数上限。 |
Codex 运行架构
Codex 负责 Agent 循环、工具循环、沙箱与短期上下文;VeADK 负责 Session 绑定、工具接入、事件转换、策略映射、可观测性与部署。
- 直连(
direct):Codex 直接调用支持 Responses API 的模型,Agent 的函数工具与 MCP 工具经 runtime 为本轮启动的本地 MCP server 交给 Codex,由 Codex 驱动工具循环。 - 会话连续性(
thread_mode="resume"):每个 Session 绑定一个 Codex thread,thread 随 Session 持久化,下一轮可在任意实例上恢复,模型看到的是 Codex 自己的完整历史,而不是回放的对话记录。 - 恢复重试:恢复 thread 遇到瞬时错误(服务繁忙 / 过载、传输已关闭)时,按退避有限次重试,仍失败才退回到用对话记录引导的新 thread;thread 不存在或参数无效时立即退回。已开始的回合从不重试,因为其中的工具可能已产生副作用。
- 转向(steer):
await runner.steer(session_id, "改用 pandas")把补充指令加入正在进行的回合,不会新开回合;回合须在当前进程中运行,否则返回False。 - 回合超时:
turn_timeout_seconds(默认 1800 秒)到期后中断回合,调用方收到TimeoutError。 - 上下文压缩:
auto_compact_token_limit交由 Codex 在 token 超过阈值时自行压缩历史。
| 后端 | 状态 | 说明 |
|---|---|---|
本地(local) | 已支持 | Codex 以子进程运行在 Agent 所在实例,thread 随 Session 持久化,适用于多实例无状态部署。 |
托管(managed) | 规划中 | Codex 回合在托管服务中运行,VeADK 只负责 Session 绑定与事件转换;接口保持不变。 |
限制
| 方面 | 说明 |
|---|---|
| Workspace 持久化 | Workspace 不跨实例、不跨重启保留。thread(对话)可在任意实例上恢复,但 workspace 默认是实例本地的临时目录;恢复后的回合发现 workspace 为空时,模型会被告知之前回合的文件可能已丢失,需要时重新生成。Workspace 持久化在规划中。 |
| 并发 | 同一进程内,同一 Session 的调用串行执行(direct + resume)。跨实例没有租约:两个实例并发运行同一 Session 时可能都会执行工具,后保存的 rollout 被拒绝,该回合在下一次恢复时依据 Session 对话记录交还给 thread。工具副作用不能重复执行时,应把同一 Session 在同一时刻只路由到一个实例。 |
max_llm_calls | direct 传输下为尽力而为:配额在每次模型调用完成后扣减,回合被中断前可能仍有一次在途请求发出。需要请求前的硬上限时使用 model_transport="shim"。 |
| Rollout 大小 | 单个 thread 的 rollout 有大小上限(默认 32 MiB,VEADK_CODEX_MAX_ROLLOUT_BYTES),超出后不再保存,下一轮依据 Session 对话记录新开 thread。短期记忆使用 local 后端时,进程内 thread 存储按最近最少使用淘汰,上限见 Codex 环境变量。 |
| 滚动升级 | 数据库中的 thread 记录存放在 veadk_codex_threads 表中,每条记录带表结构版本号。旧版本实例遇到新版本写入的记录时,不读取也不覆盖,本轮新开 thread,并记录指标 outcome=incompatible。 |
Codex 可观测性
Codex 原生生命周期和 ADK Function/MCP 工具调用都会转换为 ADK Event。运行日志使用稳定的 codex_* 事件名,并包含 invocation_id、call_id、tool、status、duration_ms 等可归因字段。日志不会记录工具参数、工具结果、API Token、凭证或后端地址;Token Usage 通过 codex_event_type=token_usage 事件及对应日志提供。
此外:
call_llmspan:ADK 的call_llmspan 本由它自己的 LLM flow 打开,而该 flow 已被 codex 替换,因此 runtime 会自行打开一个同名 span,并在回合结束时写入 prompt、响应与 token 用量。VeADK 的整条遥测链路——内存 exporter 的 Session 索引、Trace 上报、Portal 指标、以及基于 Trace 的评测——因此都能看到 codex 回合。注意粒度:每回合一个 span,而不是每次内层模型调用一个。veadk.codex.*span 属性:call_llmspan 还记录transport、thread_id、turn_id、thread_resumed、model、sandbox、approval_mode、tool_count、status与duration_ms,不包含提示词、工具参数或密钥。usage_metadata:整轮的 token 用量汇总后,只挂在每回合一个的合并后终态 Event 上(而不是逐条事件累加),避免下游做求和统计时重复计数。RunConfig(max_llm_calls=...)会被强制执行:shim 在每一次真实的后端模型调用前扣减配额。超限时 Codex 侧收到一个429 llm_calls_limit(而不是它会重试的 500——重试会把本回合的工具副作用重跑一遍),回合结束后 runtime 再向调用方重新抛出LlmCallsLimitExceededError,而不是返回 Codex 勉强拼出的半截答案。direct传输下没有 shim,Codex 在发起模型请求前也不发出任何通知,runtime 改为在每次模型调用完成后扣减配额:超出配额的那次调用会完成,此时可能已有一次请求在途,它会随回合中断而中止;之后不会再有新的调用。
指标
runtime 通过进程的 OpenTelemetry MeterProvider 上报以下指标;未配置 MeterProvider 时为空操作。属性均为低基数,不包含用户、Session 或 thread ID。
| 指标 | 类型 | 单位 | 属性 | 说明 |
|---|---|---|---|---|
veadk.codex.thread.resume | Counter | 1 | outcome:resumed/new_thread/instructions_changed/retried_then_resumed/fallback_after_error/store_error/incompatible | thread 恢复结果。 |
veadk.codex.thread.save | Counter | 1 | outcome:saved/conflict/failed/skipped/cancelled/too_large | rollout 保存结果。 |
veadk.codex.turn | Counter | 1 | status:completed/failed/cancelled/transferred/timeout;transport:direct/shim | 回合数。 |
veadk.codex.turn.duration | Histogram | s | 同 veadk.codex.turn | 回合耗时。 |
veadk.codex.turn.startup | Histogram | s | transport | 从调用开始到 Codex 回合开始的耗时。 |
veadk.codex.turn.tokens | Counter | token | transport;kind:input/output/cached_input/reasoning_output | 回合 token 用量。 |
支持矩阵
runtime="codex" 和 runtime="piagent" 会替换整个 ADK LLM 流程,因此 Agent 上相当一部分配置不会生效。VeADK 会在智能体构造时以及每次调用前检查这些配置:产生错误结果的配置直接报错(ValueError),只是被忽略的配置记录一次警告。
报错:直接拒绝运行
| 配置 | 原因 |
|---|---|
model=... | 运行时从 model_name 解析模型,完全忽略 model 对象,其 api_base、请求头和 fallback 都会丢失。请改用 model_name。 |
generate_content_config=... | 只有 system_instruction 会被转发,temperature、max_output_tokens、thinking_config 等都会被丢弃。 |
output_schema=... | schema 既不会下发给后端,也不会进入提示词,模型从未被要求按 schema 输出;state[output_key] 要么是未校验的回复,要么直接缺失。 |
planner=... / code_executor=... | 二者以 ADK 请求/响应处理器的形式运行,外部运行时不执行这一层。 |
include_contents="none" | 外部运行时始终发送完整会话历史,该设置会被静默忽略并泄漏历史轮次。 |
enable_supervisor=True | 监督流程挂在 ADK LLM flow 上,而该流程已被替换,不会有任何监督生效。 |
runtime="codex" 和 runtime="piagent" 都支持 sub_agents:VeADK 会注入一个 transfer_to_agent 工具供外部 agent 调用。把一个 runtime="codex" 智能体放进 SequentialAgent / ParallelAgent 的 sub_agents 中也支持——这种场景由父级负责调度。
警告:被忽略但仍可运行
| 配置 | 行为 |
|---|---|
model_name=[主, 备...] | 只使用第一个模型,fallback 链不生效。 |
model_provider(非 openai) | 运行时始终以 OpenAI 兼容协议访问 model_api_base。 |
model_extra_config | 仅对 piagent 成立。codex 会转发它:extra_headers 与 extra_body(含 VeADK 默认的请求加密与 prompt 缓存配置)由 shim 原样带到后端请求上,与 adk 路径一致。direct 传输只转发 extra_headers;含非空 extra_body 时 auto 会留在 shim,见 Codex 执行参数。 |
enable_responses / enable_responses_cache | 不使用 Ark Responses API,previous_response_id 续接与响应缓存均不生效。 |
example_store | 由 ExampleTool.process_llm_request 注入,外部运行时不调用该钩子,few-shot 示例不会进入 prompt。 |
knowledgebase | 知识库被静默禁用。 同样由 LoadKnowledgebaseTool.process_llm_request 注入使用说明,外部运行时不调用,模型不知道知识库的存在,检索不会被触发。 |
skills_mode | 外部运行时在桥接工具时会跳过 VeADK 的 SkillsToolset,指令里宣称的 execute_skills/skills_tool 并未注册。 |
enable_skills_checklist | 依赖 ADK 的 before-tool 回调与 skills 工具集,两者都不生效。 |
after_model_callback | 语义不同:每回合在合并后的最终文本上触发一次,而不是每次模型调用触发一次。 |
tracers | 粒度不同:codex 会补一个每回合一个的 call_llm span(含 prompt、响应、整轮 token 用量),但内层模型循环跑在外部 harness 里,仍然没有逐次模型调用的 span。 |
codex_runtime_config(配合 runtime="piagent") | 只对 codex 运行时有效,piagent 完全忽略。 |
RunConfig(max_llm_calls=...) | 仅对 piagent 成立——它的模型循环在 harness 内部自行计数,不受该配额约束。codex 已支持:见上方可观测性。 |
不支持 run_live
非 adk 运行时没有 live/bidi 实现。通过 ADK get_fast_api_app 暴露的 /run_live(veadk web 使用)对这类智能体会抛出 NotImplementedError,而不是静默回退到 ADK 流程——否则会用完全不同的模型循环和工具集执行。live 场景请使用 runtime="adk",其余场景使用 runner.run_async。
请求处理
在不侵入业务逻辑的前提下,可为每次执行插入统一的横切处理——鉴权、日志、重试、性能监控等。把处理器传入 run_processor 即可,例如接入身份认证做登录态校验:
from veadk import Agent
from veadk.integrations.ve_identity import AuthRequestProcessor
agent = Agent(name="assistant", run_processor=AuthRequestProcessor())不设置时使用默认的空处理器,不改变任何行为。