LLM 只提议
本地 Qwen 生成查询、算式、绘图参数或 Final Answer,不直接执行工具,也不决定预算与终止。
算法可视化与交互学习平台
概念上直接承接 No.8 的单次 Tool CALL:把本地模型提出的候选动作交给受约束 Runtime 校验与执行,再将真实 Observation 回灌到下一状态,直到提交证据覆盖完整的 Final Answer,或由 max_steps、工具调用、墙钟时间、input/output token 及循环检测安全停止。核心实验不依赖 No.8 的页面、语料或本地服务,而是下载一个包含 Agent、工具、知识资料、prompt、schema、测试和 manifest 的完整示例包;浏览器沿用 No.16 的本地运行方式,直连 127.0.0.1:4890 上的 algolab_local_runner.py,由用户下载的 Qwen2.5 本地模型自主完成 AgentEval Mini knowledge_search → calculator → plot → Final,并返回可按 after_seq 增量轮询的完整 execution trace。
No.8 已经完成最难的第一步:模型不再直接猜计算结果或图片,而是生成受约束的 CALL calculator {...}、CALL plot {...} 与 CALL search {...}。但一次调用结束后,模型并不知道工具究竟成功、失败,还是返回了需要继续处理的新数据;它也没有机会根据结果改写下一步。
No.17 在概念上把 No.8 末尾的 CALL → OBSERVATION → FINAL 展开成可运行系统;实验一、二仍用 No.8 的例子帮助比较单次路由与多步状态。但实验三不读取 No.8 页面、不调用 No.8 Runtime,也不依赖课程索引:它把 No.16 的“下载 algolab_local_runner.py、浏览器直连本机服务”方法提升为一个自包含 Agent 包,在 4890 内执行本地模型、工具、预算、恢复和 trace。
Agent 不是模型的别名。它是由目标、状态、模型策略、工具集合和运行时规则组成的系统。把四层混在一起,会让失败看起来都像“模型不够聪明”;分开后,每一层都有可验证的输入、输出和修复手段。
| 角色 | 它负责什么 | 它绝不能假装负责什么 |
|---|---|---|
| Agent | 保存任务目标、可见 state、允许工具、完成条件与控制策略;决定下一轮要解决哪个缺口 | 不能跳过工具,把猜测写成 Observation;也不能用流畅 Final 掩盖未完成条件 |
| Model | 读取受预算约束的 state view,在 Runtime 给定的单分支 JSON 前缀后生成工具参数值或 Final Answer | 不能直接执行工具、批准自己的参数、修改预算或宣布外部调用已经成功 |
| Tool | 在明确 schema 内完成单一能力,例如检索、精确计算或生成图表 artifact | 不规划整个任务,不决定下一工具,也不把异常吞掉后返回伪结果 |
| Runtime | 确定当前 action/final 分支,预填 type、tool/evidence_ids 与公开 Decision Summary;再验证参数、预算、执行、重试、Observation、循环和 trace | 不替模型猜查询、算式、绘图数据或 Final Answer,不把工具错误改成成功,也不请求或保存隐藏思维链 |
因此一次循环中的信任顺序是:Runtime 固定可控外壳,Model 补全任务语义,Runtime 审批,Tool 计算,Runtime 记录,Agent 再决策。只有 Runtime 能把外部执行结果写成 Observation;模型生成的“计算完成”四个字不构成事实。
第 t 轮中,模型策略 πθ 看到由 V 生成的紧凑状态视图、当前工具的内联参数 schema,并提出决策 d_t。Runtime 先校验 Decision,再校验 Action 参数:无效参数形成带 error、attempted_arguments 的失败 Observation,并进入独立的重复无效参数计数;只有参数有效后才按规范化 action signature、预算检查并分派工具。JSON/Decision 解析失败则形成 trace 事件并在同一步修复,任何失败都不会静默消失。
未知工具、缺少 expression、plot 数组长度不一致或预算不足,都应在可能产生副作用前被拒绝。
参数拒绝会形成失败 Observation;工具 timeout 或读取失败会先形成 retry/failed trace 事件,并在所有 attempt 结束后形成一个 Observation。JSON/Decision 解析失败则形成 decision.invalid 事件并进入同一步的修复提示。
Qwen2.5-1.5B、低配置备用模型和测试策略可以共享同一个 Decision contract;预算、权限、timeout、loop guard 与 trace 仍由 Runtime 强制执行。
依次点击 State₀、Decision₀、Action₀、Observation₀ 与 State₁。该概念卡与页面当前五个阶段一致:Model 提出 knowledge_search({query, top_k: 4}),Runtime 校验后分派,工具返回 calculator protocol 的证据与 structured values,下一状态才把 calculator 标记为可选动作。面板只显示可检查的 Decision Summary,不显示或保存隐藏推理;这里不承诺某个固定算式。
goal、允许工具、预算与历史 Observation 构成可见状态。Runtime 不把隐藏思维写进 state。
goal = 查课程中的 calculator 协议并计算、绘图 evidence = [] steps = 0 / 6
动态工具选择只有建立在稳定 contract 上才可控。模型应看到工具用途与参数 schema,却不必知道真实文件路径、知识文件枚举或图表落盘目录;这些生命周期由本机 Runtime 封装。实验一、二中的 search 继续承担 No.8 单次路由教学,实验三则只使用下载包内自带的三个工具,因此即使 No.8 不存在也能完成。
| 工具 | 真实参数契约 | 成功 Observation 的真实字段 | 实验三的安全边界 |
|---|---|---|---|
knowledge_search | 必填 query: string(1..500)可选 top_k: 1..4,默认 3 | query、citations[](含 id、source_id、chunk_id、text、source_sha256 等)、聚合 source_sha256、data.batches、data.release_rule 与 summary | 只枚举包内 knowledge/*.md;不访问课程 Backend,也不允许模型指定文件路径 |
calculator | 只允许 expression: string(1..240);没有 variables 字段 | expression、value 与 tool_latency_ms;整数结果直接返回整数,没有 rounded 字段 | 使用 AST 节点白名单;禁止 eval、shell、函数调用、属性访问与任意 Python |
plot | 必填 labels、values、title;可选 average、y_label | spec(kind 固定为 bar,并含 labels、values、average、title、y_label)、单个 PNG artifact(id、media_type、bytes、sha256)与 summary | labels/values 限 1..20 且等长,数值必须有限;Matplotlib 使用 Agg,trace 不塞入 base64 |
这三个工具连同 schema、实现和测试都进入 No.17 示例包;它们可以沿用 No.8/No.16 学到的契约思想,但没有运行时复用关系。模型生成候选参数,Runtime 先执行 Decision exact-field 检查和工具参数校验;工具真正完成后,语义准确性再由 expected.json 独立计入指标。
切换四个请求,检查页面当前展示的候选 Decision JSON:入门提示走 search,课程原文/citation 走 knowledge_search,已有表达式后选 calculator,已有结构化数值后选 plot。第四个候选仍故意沿用早期路由示例的 {kind, labels, values} 形状;若把它提交给实验三的真实 No.17 Runtime,会因 kind 未获允许且缺少 title 而得到 invalid_arguments,正确参数应改为 labels、values、title 与可选 average。这个反例说明“是合法 JSON、甚至工具名选对了”仍不等于参数契约有效。
{
"type": "action",
"tool": "search",
"arguments": {
"query": "tool calling JSON schema",
"top_k": 3
},
"decision_summary": "固定小语料足够完成轻量提示检索;低成本、低延迟,但不能作为正式课程引用。"
}固定小语料足够完成轻量提示检索;低成本、低延迟,但不能作为正式课程引用。
No.8 评测已经区分了 tool accuracy、valid CALL 与 execution pass。多步 Agent 还要再加一层:同一个动作在语法上合法,却可能在当前状态中完全不准确。例如尚未获得四批次数据就调用 plot,或把平均值 79 直接写进参数而没有 calculator Observation,都能通过 JSON parser,却不能推进任务。
本模块采用只含两种分支的 Decision contract;正常三工具链中的 Observation 按全局产生顺序编号:
若前面产生过参数拒绝或失败 Observation,序号会随真实列表变化,Final 必须引用运行返回的实际 ID,不能照抄上例。
expected.json 的知识、79 与图表 spec 一致。contracts.py 校验。ok: true Observation。当前 Runtime 的 Observation 使用 id、tool、ok 以及 result/error 二选一结构。下一轮消息携带最近 10 条 Observation;每条 result 序列化后最多保留约 3000 字符,并附最近 4 条 runtime_feedback 和剩余预算快照。成功结果保留数值、citation 和 artifact metadata,图片二进制只通过本机 artifact endpoint 获取。max_input_tokens 是累计模型输入的硬停止线,不等同于一个会自动把 prompt 精确裁到剩余 token 的投影器。
knowledge_search 的四批次数据与发布规则绑定 citations 中的 source_id、chunk_id、text、source_sha256 及聚合 source_sha256;calculator 的 79 绑定 expression;plot 绑定 spec 与 artifact。
参数校验失败会把 code/message 写入失败 Observation,并把更完整的拒绝说明放进 runtime_feedback;下一轮模型可据此改参。
Job 保存最多 800 个 trace 事件,模型则只看最近 10 条紧凑 Observation 与最近 4 条反馈;两者用途不同。
为了调试 Agent,我们确实需要一条完整轨迹;但“完整”指系统事件完整,而不是要求模型暴露私有、不可验证的逐字推理。No.17 保存并展示以下内容:
| 实际进入 trace / Job | 用途 | 明确不保存 |
|---|---|---|
| seq、step、attempt、phase 与 elapsed_ms | 按 after_seq 增量合并事件并还原顺序 | 本机用户名、任意绝对路径、用户秘密 |
| 普通调用使用 model.started/model.completed,repair 调用使用 model.repair.started/model.repair.completed;completed 保存公开模型描述、本次 token usage/latency 与原始输出 SHA-256,run.finished metrics 再累计所有调用 | 复查本地模型配置、Decision repair 成本与总调用成本;模型检查接口可另显示下载 manifest 中的 resolved_revision | 模型绝对目录和任一次模型输出正文 |
decision.valid/decision.repaired 中的规范化 Decision JSON 与 Runtime 预填的简短 Decision Summary;decision.invalid 中的 code/error、缺失契约字段和意外字段数量 | 复查 action/final contract 与恢复次数,同时解释字段集合错误而不暴露字段值 | 模型意外输出的额外字段值、要求模型输出“完整思考过程”的字段或隐藏思维链 |
| tool.started/retry/failed/rejected/observation、arguments、Observation、error、tool latency 与 artifact metadata | 判断工具是否真的分派、是否重试及最终产生了什么 | 图片 base64、任意 shell/代码或清单外路径 |
| observation_count state_delta、预算快照、run.finished 的 status、stop_reason、Final、metrics 或结构化终态 error | 诊断状态推进、循环、效率与终止;模型加载失败和取消也不会让 trace 戛然而止 | 把模型声称的结果覆盖到工具事实之上 |
v2.6 的 Decision Summary 由 Runtime 根据当前缺口生成,例如“根据成功 Observation 计算所需数值”,而不是向模型索取内部独白。Runtime 不把模型原始 JSON 正文写进 trace:正常与 repair 调用都只留各自 SHA-256;解析成功后才保存经过精确字段校验的 Decision。图表在 JSON 中只保存 artifact ID、bytes、media type 与 hash,图片由本机 endpoint 单独返回。
当前 Runtime 强制五类上限:外层 Decision step、实际工具 attempt、整次墙钟时间、累计 input tokens 与累计 output tokens。它在每轮前检查已用预算,把同一绝对 deadline 传给正常/repair 模型调用,并按剩余 output token 收紧 max_new_tokens;工具 attempt 的等待上限取 tool_timeout 与剩余总时间的较小值。Cost Units 仅在结束时由 token 数计算为效率指标,不是可配置预算。
即使每个工具都很快且免费,模型仍可能在两个合法动作之间来回切换;step 上限提供最后一道确定性边界。
工具 retry 保持同一个 outer step,但每个实际 attempt 都增加 tool_calls 和墙钟耗时;JSON repair 同样不增加 step,却会增加 model_calls 与 input/output tokens。
Runner 使用本地 tokenizer 返回每次模型调用的 input/output tokens,并将模型耗时和工具耗时分开。Cost Units 只是上述固定公式的归一化 token 指标,不得标成真实货币费用或成本预算。
Agent 的输出分为两类:Final Answer 表示模型认为任务已完成,controlled stop 表示 Runtime 因安全、故障或资源边界终止。当前协议同时返回顶层 status 与 stop_reason,二者不能混写。
| status / stop_reason | 当前实现的触发条件 | Task Success | Termination Correctness |
|---|---|---|---|
completed / completed | Final 之前已有三个工具各自至少一个成功 Observation,且 evidence_ids 覆盖每个工具最后一条成功 Observation;缺证据的过早 Final 只写 feedback 并进入下一 step | expected.json 再独立检查知识值/规则、按 knowledge_search→calculator→plot 的因果顺序、calculator=79、plot spec 和 Final 中的 79、72 及“满足/eligible/pass”结论 | 当前实现把有 Final 的 completed 终止记为 true;它与 Task Success 是两个不同指标 |
stopped / max_steps、tool_budget、token_budget 或 timeout | 外层 step 用尽,实际工具 attempts 用尽,累计 input/output token 到线,或总墙钟时间到线 | 没有 Final,故为 FAIL | 这些 reason 在 ground truth 的保护性停止白名单中,记为 true |
stopped / loop_detected | 同一组无效参数达到重复阈值、同一规范化有效 action 第 N 次出现,或同一成功结果摘要第 N 次出现;重复无效参数和重复结果发 guard.no_progress,有效 action 重复发 guard.loop_detected | FAIL | 属于保护性正确停止,记为 true;Trace 会进一步说明工具是否曾真正执行 |
failed / invalid_decision | JSON/Decision 在 max_retries 次即时 repair 后仍无效 | FAIL | 属于保护性正确停止,记为 true |
cancelled / cancelled | 运行中取消会由 Runtime 返回完整结果;queued Job 也会被 JobStore 直接封口并追加 run.finished | 运行中取消为 false;queued 取消尚未进入 Runtime,metrics 为 null | 运行中取消属于保护性正确停止;queued 取消显示 N/A |
failed / model_unavailable | 模型目录、依赖、设备/dtype 配置或本地权重加载失败;错误信息与 trace 都会脱敏绝对路径 | 尚未进入有效决策,metrics 为 null | N/A;JobStore 仍追加结构化 run.finished |
failed / runtime_error | 非模型供应问题的未预期 policy/worker 异常 | metrics 为 null | N/A;错误保留公开 code/message 并追加 run.finished |
核心实验的完成清单不是一句“我完成了”,而是五个可复算条件:knowledge_search 返回 Batch A–D 的 72、78、81、85 与 average_gte=78、every_batch_gte=70;calculator 得到 79;plot spec 含完整 labels/values 与 average=79;三个正确 Observation 按因果顺序出现;Final 至少包含 79、72 和满足发布的结论,并用 evidence_ids 覆盖本次三个工具的真实成功 Observation。
部署在远端的 AlgoLab Backend 无法主动访问用户电脑的 127.0.0.1。因此实验三严格沿用 No.16 已验证的连接方式:服务器只提供页面、源码、ZIP、manifest 与 SHA-256;页面中的浏览器代码再直接请求本机 http://127.0.0.1:4890。prompt、知识资料、模型权重、Observation、trace 和图表默认不上传服务器,而且前端协议会拒绝非 localhost/127.0.0.1/::1 的 Runner URL。
模型由用户显式运行 download_model.py 下载。默认使用 Qwen2.5-1.5B-Instruct,低配置可选 0.5B;Runner 只用 local_files_only=True、trust_remote_code=False 从本地目录加载,并跨同一进程中匹配路径/device/dtype 的多轮调用复用 tokenizer/model。页面先用 POST /models/inspect 检查 config、tokenizer 与权重文件,并预检 torch/transformers、device 与 dtype 的可用性;检查失败就显示错误并禁用启动,不切换到写死结果。
实验三不是一段只适合阅读的伪代码,而是一份可下载、可测试、可独立启动的最小 Agent 工程。页面不把几段源码复制进模块 JSON;它读取服务器即时枚举的真实文件清单,因此学习者看到的内容、逐文件下载的内容和 ZIP 中实际运行的内容始终同源。点击左侧任一文件后,中列显示该文件全部真实内容,右列同步说明文件作用、关键实现与它在完整行动链中的衔接;未知的后续新增文件也会显示明确的通用说明,而不是空白。
| 层 | 示例包中的文件 | 可复查的问题 |
|---|---|---|
| 启动与 HTTP | algolab_local_runner.py、agent/server.py、agent/job_store.py | 4890 怎样暴露 status、schema、model inspect/unload、创建/轮询/取消 Job 和返回 artifact;请求体怎样限制为 256 KiB |
| Agent 控制 | agent/contracts.py、agent/runtime.py、agent/model_policy.py、agent/tools.py | Decision 怎样校验,Observation 怎样回灌,预算、repair、retry、loop 与 Final 怎样被强制执行 |
| 模型环境 | download_model.py、requirements.txt | 用户怎样显式下载 Qwen、记录 resolved revision/文件 SHA,并让 Runner 以 local_files_only 加载 |
| 契约与上下文 | prompts/、schemas/decision.schema.json、schemas/tool_registry.json | 模型可见哪些字段,为什么只允许 action/final,以及 Runtime 怎样拒绝 reasoning 等额外字段和越权工具 |
| 本地知识与验收 | knowledge/agent_eval_mini.md、data/expected.json | 工具只从 markdown 解析 72、78、81、85 和双阈值;expected.json 只供 Runtime 复算指标,不会喂给模型或工具 |
| 复现与供应链 | tests/、run.ps1、run.sh、README.md、manifest.json | 正常链、故障链和路径安全怎样测试,静态 manifest 怎样声明版本,服务器怎样为真实文件计算 bytes/SHA-256 |
Backend 枚举示例目录时排除 .git、.runs、.venv、__pycache__、models、symlink 和 pyc,并限制文件/包大小;单文件接口只接受该次枚举清单中的规范相对路径,拒绝 ..、绝对路径和清单外文件。ZIP 以 react_agent_local/ 为根目录。模型权重不打进 ZIP,而由 download_model.py 显式获取并保存到用户选择的目录。
先下载完整 No.17 示例包和可选的 Qwen2.5-1.5B-Instruct(低配置可用 0.5B),启动包内 algolab_local_runner.py;服务器提供的页面由浏览器直接连接 127.0.0.1:4890。默认任务要求 Agent 查询包内 AgentEval Mini,取得 Batch A–D 的 Task Success 72、78、81、85 及 average_gte=78、every_batch_gte=70;本地模型必须自主提出 knowledge_search,再根据 Observation 生成 calculator 的 (72 + 78 + 81 + 85) / 4,随后用 plot 的 labels/values/average/title 参数绘制四批次和 79% 平均线,最后引用实际 evidence_ids 给出是否发布的 Final。页面展示模型检查信息、queued/running/completed/failed/cancelled/stopped 状态、Decision/拒绝事件、Observation、observation_count state delta、预算、token、模型/工具耗时、指标、图表和真实 stop_reason,并提供取消、导出 trace、完整源码浏览及 ZIP 下载;模型或 Runner 缺失时不使用固定策略或固定结果兜底。
统一端口 4890;需使用示例包中带 agent capability 的 algolab_local_runner.py。
推荐 Qwen2.5-1.5B-Instruct;低配置可选 0.5B。模型目录可位于任意项目或磁盘;依赖检查针对上方当前 4890 Runner 的解释器,而不是根据模型目录推断 Python 环境。
一次设置 12 steps、10 次工具、300 秒、32k / 4k 累计输入输出 token。v2.6 Runtime 会把工具参数 schema 与公开 Decision Summary 固定在当前契约前缀中,让小模型只补全参数值或 Final Answer。
input tokens 是每轮完整状态提示的累计量,不是单轮模型上下文长度。重复 Action、重复未通过 Final 与无进展 Observation 都受 Runtime 保护;阈值仍限制在安全范围 2–8。
根目录文件负责准备与启动,agent/ 承担真实控制循环,prompts/ 与 schemas/ 约束 LLM 的候选 Decision,knowledge/ 提供工具可检索事实,data/expected.json 在运行后独立评分,tests/ 则验证整条链路。
本地 Qwen 生成查询、算式、绘图参数或 Final Answer,不直接执行工具,也不决定预算与终止。
Runtime 组装有界状态、预填公开 JSON 外壳、校验 Decision、调度工具并推进下一状态。
knowledge_search、calculator 与 plot 的真实返回值才会进入下一轮;模型声称成功不能替代 Observation。
data/expected.json 只在运行结束后复算指标,永远不会进入 LLM prompt、检索结果或工具参数。
点击任一文件标签,会在下方同时打开完整内容和逐文件解释。
告诉用户如何安装依赖、显式下载模型、核对包版本,并用自己指定的 Python 启动 4890。
从 HTTP Job 到模型适配、严格协议、工具执行和 state → action → observation 循环的核心代码。
前者告诉模型当前只能怎样续写,后者以机器可读形式公开 Decision 与工具参数边界。
knowledge 是 Agent 经工具可见的事实;expected 是 LLM 不可见的验收标准,两者用途不同。
不用下载大模型即可验证完整闭环、错误恢复、预算、循环检测、安全边界与评分隔离。
箭头表示本轮执行中的调用或数据推进;同一文件可在不同泳道承担不同职责。横向内容可滚动查看。
用户指定的 Python 从启动脚本进入 loopback HTTP 服务,再把异步 Job 交给行动循环。
按说明安装依赖,并把允许的 Qwen 显式下载到用户选择的目录。
操作系统脚本只使用用户给定解释器。
不猜环境,只转交 server.main。
提供检查模型、创建/轮询/取消 Job 与 artifact 路由。
维护 queued/running/terminal、取消事件和公开快照。
执行预算、恢复、循环检测与 Final 终止。
每一步只把当前允许的分支、成功 Observation 和剩余预算交给本地 Qwen;输出始终只是候选 Decision。
Runtime 选择 action/final/repair 提示,并内联当前唯一参数契约。
应用 assistant JSON 前缀、token/deadline,并只从本地目录加载权重。
续写查询、算式、绘图参数或 Final Answer。
拒绝未知工具、额外字段、错误参数和不完整 Final。
合法 Action 才能执行;失败信息受限回灌并受预算约束。
边界:LLM 看得到 prompt、当前 schema 与公开 Observation;看不到 expected.json,也没有直接调用 Python 工具的权限。
严格校验后的 Action 由 Runtime 分派,工具的结构化结果再回到下一轮状态。
工具名与参数已通过白名单协议。
在调用前检查 tool/time/token/retry 与重复动作预算。
检索本地知识、计算表达式或生成图表和 PNG。
真实 result/error、证据 ID 与状态增量进入下一轮。
Runtime 压缩公开 Observation,再请求下一次 Decision。
边界:知识文件不会直接塞进 prompt;只有 knowledge_search 选中的片段与解析数据会成为 Observation。
正常结束与任务成功分开判断:Final 证据覆盖可以让 Run completed,语义是否完整仍由 ground truth 复算。
保留模型答案、证据引用与真实工具结果。
保存数值、阈值、最低值与受保护终止原因。
复算 Task Success、工具准确率、恢复率、循环率与终止正确性。
浏览器呈现 trace、Observation、图表和指标,不伪造成功。
边界:expected.json 与 knowledge/ 内容可以数值对齐,但没有 expected → LLM 的数据边;这是防止答案泄露的关键边界。
正常链路不是一条写死的动画,而是本地模型根据当前 state 缺口逐轮提出 Decision。运行实验后,应能沿 trace 复查以下因果关系;表中的数字是 expected.json 的确定性验收目标,不是 Runtime 预先喂给模型的行动脚本。
| 阶段 | Decision、Action 与真实 Observation 形状 | 它怎样改变 next state / metrics |
|---|---|---|
| 0 · 检查与 Job | 页面先调用 /models/inspect 核对本地 config、tokenizer、weights、推理依赖与 device/dtype;再以 202 创建 /agent/runs queued Job,首次模型调用时才真正加载/复用模型 | Job 公共字段只记录 model name、path hash、device、dtype 与 budgets;模型检查可显示下载 manifest 的 resolved_revision |
| 1 · 本地知识 | Qwen 提出 knowledge_search {query, top_k};工具返回 citations、source_sha256、Batch A=72/B=78/C=81/D=85 与 average_gte=78/every_batch_gte=70 | 产生如 obs-knowledge_search-1 的成功 Observation;下一轮 compact state 能看到事实与规则 |
| 2 · 精确计算 | Qwen 根据上一 Observation 提出 calculator {expression: "(72 + 78 + 81 + 85) / 4"};result.value 必须是 79 | 产生如 obs-calculator-2;value=79 才会增加 accurate_actions |
| 3 · 图表 artifact | Qwen 提出 plot,传入 labels=[Batch A,Batch B,Batch C,Batch D]、values=[72,78,81,85]、average=79、title;返回 spec 与 PNG artifact | 产生如 obs-plot-3;页面既用 spec 绘制课程图,也从本机 artifact endpoint 打开真实 PNG |
| 4 · Final | Qwen 引用三个实际 Observation ID,并说明平均 79≥78 且最低 72≥70,因此满足发布条件 | 证据覆盖通过时返回 status=completed、stop_reason=completed;随后 Task Success 依据 expected 的知识、计算、plot spec 与回答关键词复算 |
若知识文件缺失、读取失败或格式损坏,工具会返回带 code 的失败 Observation;模型可在下一 Decision 改变动作,只有 retryable 的工具失败才由 Runtime 自动重试同一 action。直接 Final 或引用不存在/非最新成功 Observation 会被当作 premature Final 回灌;算错或漏画 average 仍可能满足“有三个成功工具”的 Final 证据门,但最终 Task Success 会是 FAIL,这正体现协议终止与任务正确性必须分开评测。
“再试一次”不是通用恢复策略。当前 Runtime 把恢复分成三条真实路径:Decision 即时 repair、下一 step 参数修正、同一工具 action 的自动 retry。它们使用不同计数方式。
| 错误层 | 示例 | 当前实现 | 终止/指标结果 |
|---|---|---|---|
| Model output / Decision | Qwen 在 JSON 后附解释、缺少 type、增加 reasoning 字段或选择未知工具 | 写 decision.invalid;把错误与最多 3000 字符的无效输出只放入 repair prompt,在同一个 outer step 内最多生成 max_retries 次;每次 repair 模型调用也写 model.started/completed/failed,正文只留 SHA | 全部失败为 failed / invalid_decision;成功 repair 增加 Recovery 的 recovered 计数,原始输出正文不进 trace |
| Tool arguments | calculator 使用 formula;plot.values 与 labels 不等长;knowledge_search.top_k=8 | 执行前写 tool.rejected 与 ok:false / code=invalid_arguments Observation;下一 outer step 的普通模型调用从 feedback 改参,不走同一步 repair prompt | 拒绝不增加 tool_calls;以后出现合法参数或接受的 Final 时,挂起的可恢复失败才计 recovered |
| Semantic result / causal order | expression 合法但 value 不是 79、plot 缺 average=79,或 calculator 出现在正确 knowledge_search 之前 | 工具照常返回成功 Observation;Runtime 不自动改参数,而是在当前 prior observations 下计算 accurate_actions,并在 Task Success 中检查 knowledge_search→calculator→plot 顺序 | Valid Action 可增加而 Tool/Argument Accuracy 不增加;有证据的 Final 仍可能 completed,但 Task Success=FAIL |
| Retryable tool failure | tool_timeout 或显式 retryable=True 的 knowledge_read_failed/测试故障 | 相同规范化 action 在同一 outer step 内最多再执行 max_retries 次;每次写 tool.retry/failed 并消耗一个 tool_call。每次等待取 tool timeout 与剩余总时间的较小值 | 后续 attempt 成功才把这些 recoverable failures 计入 recovered;总 deadline 先到则以 timeout 停止且不继续 retry |
| Runner / model failure | 模型目录、当前 4890 Runner 解释器中的 torch/transformers、device/dtype 或本地权重加载失败 | 页面模型检查通常先返回 HTTP 400,并区分依赖缺失与已找到但导入失败;这只描述用户指定 Python 启动的当前 4890 进程,不代表电脑上的其他虚拟环境。若 worker 内失败,Job 错误会脱敏本地模型路径并追加终态 run.finished | failed / model_unavailable;只有非模型供应问题的未知异常使用 failed / runtime_error |
测试覆盖 invalid JSON repair、invalid arguments 下一步修正、retryable 工具错误、tool timeout、总 deadline、retry 耗尽、因果顺序、重复 action、无进展、各类预算、取消、终态 trace 与模型路径脱敏。需要特别诚实的一点是:Python thread 的 future.cancel() 不能保证中断已经开始的工具函数;当前 timeout 会停止等待并阻止额外 retry,但生产写入型工具还应增加真正的可取消 I/O 或进程隔离。
同一个 max_retries 同时限制两种不同机制:解析失败时在同一 step 内重新调用模型修 Decision;工具执行失败时在同一 step 内重放同一规范化 action。INVALID_ARGUMENT 已经是一个可解析 Action,因此不会进入前者,而是形成 Observation 后等待下一 step 的新 proposal。
示例 plot artifact 使用随机 ID;正常成功只返回一次,但 timeout 后底层线程若仍完成,可能留下未关联文件。生产写入型工具应增加 idempotency key 或进程级取消。
工具 retry 保持同一规范化 action 和 step,只增加 attempt/tool_calls;invalid_arguments 修正则是下一 step 的新 proposal,必须重新校验。
教学实验没有指数退避;它依靠 0..3 的有限 extra attempts、tool-call 上限和每次 attempt 前的总时间检查来封顶。
Agent 常见的失败不一定抛异常,也可能反复提交同一组无效参数,或每一步都合法却始终没有新信息。v2.5 Runtime 将三种现象分开计数,避免把“参数从未通过校验”误报成“工具已执行后的动作循环”。
| 现象 | 当前计数方式 | 处理 |
|---|---|---|
模型重复提交 knowledge_search arguments={} | 每次先生成带 validator error 与 attempted_arguments 的失败 Observation,再累计同一 invalid signature | 达到阈值发 guard.no_progress、code=repeated_invalid_arguments;工具调用数仍为 0 |
| INVALID_ARGUMENT 后把 formula 改为 expression | 原始无效参数改变,新的 proposal 重新校验;不会污染有效 action 计数 | 若校验并执行成功,挂起的恢复计数可转为 recovered |
| 一次 tool_timeout 后自动执行 attempt 2 | retry 发生在同一个 outer Action 内,不再次累计 valid action signature | 由 max_retries/tool_calls 管理,trace 以 attempt 区分 |
| 模型在不同 step 第 N 次提出完全相同且校验通过的 tool + arguments | 规范化参数的 valid action signature 第 N 次出现时在分派前触发;阈值可设 2..8 | guard.loop_detected,不执行该次工具 |
| 不同 query 让 knowledge_search 返回相同 data,或 plot 省略与明示默认 y_label 后得到相同规范化 spec | 只对成功结果的稳定摘要计数,忽略 latency、artifact 随机 ID/hash;不是“连续 streak”要求 | 工具已执行后发 guard.no_progress,但 stop_reason 仍为 loop_detected |
当前实现不会直接识别任意 A→B→A→B 序列;只有当某个 invalid signature、valid action signature 或稳定 result digest 自身达到阈值时才停止。Loop Rate 也不是重复事件总数:本次运行一旦触发任一 guard,就报告 1 / action_attempts;未触发则为 0,工具 retry 不进入 action_attempts。
这些值是当前单次 Job 的 metrics,不是数据库中跨 N 次运行自动聚合的均值。Task Success 检查 Final 与三类语义结果;Valid Action Rate 和 Tool/Argument Accuracy 的分母都是已解析出的非 Final Action 数;Recovery Rate 使用 Runtime 的可恢复/已恢复计数;Loop Rate 是触发任一循环 guard 的指示量除以 Action 数;Termination Correctness 按 completed Final 或 expected.json 的保护性 stop_reason 判定。Steps、Latency、Tokens 与 Cost Units 另报效率。
没有故障时 Recovery Rate 不是 0%;没有 action proposal 时 Valid Action Rate 也不是 0%。N/A 能避免把“未运行”误读成失败。
每个 outer Decision(包括 Final 与参数拒绝)增加 step;JSON repair 增加 model_calls 但不增加 step;同动作工具 retry 增加 tool_calls 但不增加 step。
Latency 是真实墙钟总耗时,并报告累计 model/tool latency;Tokens 来自本地 tokenizer 的所有正常与 repair 调用;Cost Units 固定为 (input+2×output)/1000,不冒充货币账单。
这是下载包中 agent/runtime.py 的忠实缩略顺序:outer step、Decision repair、参数 schema 校验、重复无效参数保护、规范化有效 action 保护、工具 retry、结果无进展保护与 Final 证据门分别出现。完整可运行版本及 contracts、model_policy、tools、job_store、prompt、schema、知识资料和测试都在实验源码浏览器与 ZIP 中;浏览器只创建/轮询 Job,不在前端伪造状态转移。
for step in range(1, budgets['max_steps'] + 1):
reason = stop_reason(budgets, usage, step - 1, started)
if cancel.is_set() or reason:
status, stop_reason = protected_stop(cancel, reason)
break
# Runtime exposes one state-specific branch. For Action it embeds the
# required tool's parameter schema and prefills through its first key.
generation = policy.generate(
messages(task, observations[-10:], feedback[-4:], budget_snapshot),
max_new_tokens=budgets['decision_max_new_tokens'],
temperature=temperature,
cancel_event=cancel,
)
add_model_usage(usage, generation)
if token_or_time_budget_exhausted():
status, stop_reason = 'stopped', budget_reason
break
try:
decision = parse_decision(generation['text'])
except DecisionError as error:
decision = repair_decision_up_to_limit(error) # same outer step
if decision is None:
status, stop_reason = 'failed', 'invalid_decision'
break
emit('decision.valid', decision=decision.__dict__)
if isinstance(decision, FinalDecision):
accepted, message = accept_final_evidence(decision, observations)
emit('final.validated', accepted=accepted, message=message)
if accepted:
status, stop_reason = 'completed', 'completed'
final_answer = decision.answer
break
feedback.append(message)
guard_repeated_premature_final()
continue
action_attempts += 1
try:
arguments = validate_tool_arguments(decision.tool, decision.arguments)
except DecisionError as error:
observation = rejected_observation(
decision.tool, error, attempted_arguments=decision.arguments)
observations.append(observation)
emit('tool.rejected', observation=observation)
feedback.append(str(error))
invalid_signature = canonical_json(decision.tool, decision.arguments)
if invalid_occurrence(invalid_signature) >= budgets['repeated_action_limit']:
emit('guard.no_progress', code='repeated_invalid_arguments',
validator_error=observation['error'])
status, stop_reason = 'stopped', 'loop_detected'
break
continue # next state receives the failed Observation
valid_signature = canonical_json(decision.tool, arguments)
if valid_occurrence(valid_signature) >= budgets['repeated_action_limit']:
emit('guard.loop_detected', repeated_action={'tool': decision.tool,
'arguments': arguments})
status, stop_reason = 'stopped', 'loop_detected'
break # this valid action is not dispatched again
guard_tool_calls_tokens_and_time()
for attempt in range(1, budgets['max_retries'] + 2):
usage['tool_calls'] += 1
try:
result = execute_with_timeout(decision.tool, arguments)
break
except ToolError as error:
emit('tool.retry' if can_retry(error, attempt) else 'tool.failed')
if not can_retry(error, attempt):
result = None
break
observation = success_or_failure_observation(decision.tool, result, error)
observations.append(observation)
emit('tool.observation', observation=observation,
state_delta={'observation_count': len(observations)})
if result is not None and result_occurrence(stable_result_digest(result)) >= budgets['no_progress_limit']:
emit('guard.no_progress', code='repeated_result')
status, stop_reason = 'stopped', 'loop_detected'
break
else:
status, stop_reason = 'stopped', 'max_steps'
metrics = compute_metrics(expected_json, counters, usage, status, stop_reason)当前模块的可验收闭环:
failed/model_unavailable,不使用固定 Decision 兜底。127.0.0.1:4890 上的 Agent Job。本地 prompt、知识、trace 与 artifact 默认不上行。max_retries 次;invalid_arguments 形成含 error、attempted_arguments 的失败 Observation,下一 state 还会明确缺失字段,由下一 step 修正;同一无效参数达到阈值时发出 repeated_invalid_arguments,且不会伪造工具调用。retryable 工具错误在同一 step 重放 action。正常 Final 是 completed/completed;预算、重复 action 和无进展结果使用 stopped 及对应 reason(无进展最终也是 loop_detected);取消、invalid_decision、model_unavailable 与 runtime_error 都以协议中的真实 status/reason 和 run.finished 封口。data/expected.json 计算 Task Success、Valid Action Rate、Tool/Argument Accuracy、Recovery Rate、Loop Rate、Termination Correctness、Steps、Tool/Model Calls、模型/工具 Latency、input/output Tokens 与 Cost Units;分母为零返回 null,页面以 N/A/未运行语义展示。/rag/*。仍需明确的工程边界:页面把 temperature 设为 0,并限制单轮 Decision 输出 256 tokens,但小模型仍可能选错工具或参数;0.5B 是低资源备用而非与 1.5B 等精度。首版基于 Transformers 本地目录,不同时引入 GGUF/llama.cpp。AgentEval Mini 是用于学习控制循环的可复现小知识库,不代表生产 RAG;当前 thread timeout 也不是强制杀死已经运行的 Python 工具。生产系统还需要真正可取消的工具隔离、权限过滤、持久索引、并发调度、观测脱敏和更严格的模型供应链校验。Cost Units 固定为 (input+2×output)/1000,只表示相对 token 计算量,不是预算或货币账单。
正在检查登录状态与模型配置…