编排层写两种意图:要一段模型输出,和要一个自主干活的工作者。两种意图两个名字:
llm() 是一次模型调用,agent() 是带工具循环的完整 turn。
agent 就是循环里的 llm:它的每一轮说话走的是同一个单次调用层。
runtime.exec 退回基础设施层(provider 传输、嵌入、测试),编排与业务代码不再直接使用它。
reply = llm("把下面的段落压缩成三句话:\n" + text)
verdict = llm(
[{"type": "text", "text": "这张截图里登录按钮在哪?给出坐标。"},
{"type": "image", "source": …}],
model="gemini:gemini-3-pro", effort="low")
| 方面 | 定义 |
|---|---|
| 签名 | llm(prompt, *, model="", effort="", response_format=None, choices=None,
web_search=False, timeout_s=None) -> str | dict。prompt 收 str 或 content blocks
(text/image 混排),str 在入口转成一个 text block。 |
| 语义 | 恰好一次模型请求(传输层重试不算第二次)。没有 session 分支、没有 transcript、
没有工具循环。返回模型输出本身,不带 [spawned agent …] 尾巴。 |
| 覆盖优先级 | 调用处参数 > 会话/runtime 默认。不给 model 就用当前会话的模型。 |
| runtime 来源 | 环境获取:agentic function 执行时框架已把当前 Runtime 放进
contextvar(_current_runtime),llm 从那里拿。函数签名不再串
runtime: Runtime 参数。 |
| 归属 | openprogram/agentic_programming/ 导出,和
agentic_function 同层。DAG 里就是现在的 ModelCall 节点。 |
| 实现 | 薄封装:对环境 runtime 调 exec(content=…, tools=[], max_iterations=1) 一类的
单次形态。runtime.exec 的工具循环参数(tools/toolset/
max_iterations/tool_choice)不出现在 llm 上——要循环就用 agent。 |
公开形态不变:agent(prompt, description="", agent_id="", start_from="clean",
run_in_background=False, …),模型与工具集来自 agent_id 选的 profile,
运行建 session 分支、受资源限额管、DAG 记独立分支(倒三角)。本次完善两件事:
| 完善 | 内容 |
|---|---|
| 说话走单次调用层 | 循环体内每轮的模型请求与 llm 走同一层,
共享认证、重试、usage 统计和测试注入缝。llm 与 agent 的差异只剩循环和 session,没有第二条请求通道。 |
| 可观测性分开计数 | 每次调用记录 execution_kind = llm | agent、
provider 请求数、agent 迭代数。llm 的传输重试不计入迭代数,"一次调用"可验收。 |
编排与业务代码里每个 runtime.exec(…) 按一条规则改写:
llm(…);带 tools/toolset/
max_iterations>1(少数:wiki ingest、deep_work 的工作步、research 的 survey 步)
→ 保持工具循环语义,改写为 agent(…) 或暂以 llm 之外的既有路径运行,逐处判断。
函数签名里的 runtime: Runtime 参数随迁移删除,调用链同步收紧。
| 批 | 范围 | 说明 |
|---|---|---|
| 1 | 新增 llm()(agentic_programming 导出)+
agent 说话改走单次调用层 + 计数 | 含单测:一次请求、content blocks 原样进 provider、
model/effort 覆盖不改全局、无 session 分支产生。workflow 注入环境加入 llm。 |
| 2 | 本体编排代码:functions/agentics/(deep_work、ask_user、
extract_pdf_*、research/、示例)、memory/wiki/ | 约 20 个文件;对应单测同批适配。 |
| 3 | 嵌套 harness 仓库(functions/agentics/ 下):Research-Agent-Harness
(约 80 处,stages 各目录)→ GUI-Agent-Harness(planning / benchmarks,截图调用靠 content blocks)→
Wiki-Agent-Harness | 逐仓一个子任务,嵌套仓库随改随同步。 |
| 4 | 收尾 | 编排层无 runtime.exec 残留;examples/ 与
provider/agent 内核明确保留 Runtime(嵌入与实现材料);产品文档与本页同步。 |
每批全绿才进下一批:批 2、3 是大面积机械替换,语义错误会成倍放大,串行是最便宜的隔离。
对 references/ 下八个框架的分层做了逐源码调研,支撑本设计的三条结论:
| 结论 | 证据 |
|---|---|
| 成熟框架都保留独立的单请求层 | pi-mono/pi-ai completeSimple、OpenCode
LLM.stream、Claude Code queryModelWithoutStreaming/queryWithModel、
Codex ModelClientSession.stream;agent loop 只是这一层的循环使用者。 |
| "无工具⇒单轮"不能当永久语义 | Codex 的继续条件是 model_needs_follow_up ||
has_pending_input,stop hook / compaction 都能让无工具 turn 继续;Claude Code 同。
所以单次调用要有自己的名字(llm),而不是 agent 的一个退化档位。 |
| 分流点必须在 session 设施之前 | 反例 OpenClaw:轻量 promptMode=none
在完整 runner 里较晚生效,session/资源/事件设施照建,省不下固定开销。llm 的路径上不建这些。 |
| 决定 | 理由 |
|---|---|
| 两个名字,不是一个 agent() 内部分流 | 调用方的意图本来就是两种;用"profile 带不带工具" 推断执行路径是间接信号,调研证明它在多个框架里不成立。名字直接把意图写进代码。 |
llm 不带工具参数 | 带上工具它就是另一个 agent 入口,两个原语的边界就没了。 需要工具循环的调用属于 agent。 |
| 测试缝放 provider 调用边界 | 公开的 llm()/agent() 不暴露测试参数;
注入发生在 Runtime 构造层(Runtime(call=…) / stream_fn),
两条路共享同一个缝。 |
runtime.exec 退层不删除 | 它是 llm 的实现材料,也是嵌入
(Runtime(call=mock_llm) 独立运行)与 provider 内核的接口;examples 演示的就是这层,不迁。 |
| runtime 环境获取,不串参数 | ~100 处调用的签名都在串 runtime: Runtime,
这是管道噪音;contextvar 已存在(_current_runtime),llm 直接用。 |
openprogram.agentic_programming.llm 已导出:环境 Runtime
的单次调用形态,不创建 session 分支或 transcript;str prompt 入口转单个 text block,content blocks
原样进 provider 层;model/effort 仅覆盖本次调用。llm 的
ModelCall 节点与 agent 的 assistant 节点记录 execution_kind、
provider_request_count、agent_iteration_count,传输重试不计迭代。
agent 与 llm 共用 AgentSession → agent_loop → provider stream 单次调用层。
workflow 注入环境含带 checkpoint 的 llm。
llm()
(Research 92 处、GUI 23 处、Wiki 1 处、本体约 30 处)。编排层仍在 runtime.exec
上的只剩 9 处带工具循环的调用(deep_work 工作步、llm_call_example 示例、Wiki ingest 生成/enrich、
Research utils/wiki survey/run_experiment 等),均带
tool loop, migrates with agent() in a later batch 注释,等 agent 批次逐处判断。
openprogram/memory/wiki/ 死代码目录(f4147247 换 Markdown workspace 后的磁盘残留,
无调用方)已删除。