OpenProgram · Provider / Runtime 设计文档 · implemented and reviewed · 2026-08-12
JSON Schema Structured Output
设计基线:main@708bfbab。实现证据:Tasks 1–6,最终审查提交 2fb471b3。第 2–15 节保留获批设计与实施清单;第 16 节区分已实现合同、验证证据与未满足的完整门禁。
设计结论:继续使用现有 Runtime.exec(..., response_format=...) 作为唯一公共入口;在 provider 公共类型中加入规范化后的 schema 配置,由 capability negotiation 选择 provider 原生 JSON Schema 或受约束的隐藏 tool fallback。无论 provider 是否宣称 strict,最终结果都必须经过严格 JSON 解析和调用方原始 JSON Schema 的本地校验。失败最多执行一次语义修复请求,最终只返回已校验的 Python JSON 值。
当前实现已经沿既有 Agent loop 完成 schema 规范化、能力协商、严格本地校验、一次可选修复、CLI/Web typed 入口、record/replay v2 和 session title consumer。未验证的 provider 组合保持 fail closed;普通文本调用保持原返回语义。
1. 范围、目标与边界
统一语义
Python Runtime、Agent、CLI、WebSocket/API 使用同一份 schema、相同协商结果、相同验证器和错误分类。
流式兼容
保留 provider 的增量文本和 tool 事件,同时明确候选尝试、丢弃、重试与最终结构化结果事件。
可复现
record/replay 记录规范化请求和每次 provider 调用;离线 replay 重新执行同一解析、校验与有限重试。
不在本次实现范围:任意 schema 到 provider 子集的有损改写、自动生成 Pydantic 类型、跨语言代码生成、逐 token 的增量 JSON AST、Web UI schema 编辑器、改变普通文本调用的返回值或流事件。
2. 设计基线审计(main@708bfbab)
本节记录立项时的事实与缺口,作为后续设计决定的来源,不描述 2fb471b3 的当前状态。
| 层 | 当前事实 | 缺口 |
|---|---|---|
| 公共 Runtime | agentic_programming/runtime.py 已暴露 response_format: dict,并在 callable-model 路径透传。 | _call_via_providers() 没有使用它;默认 provider 调用静默退化为普通文本。 |
| Provider 类型 | StreamOptions 有采样、tool、transport、header 等字段;Model 有 reasoning/thinking/compat;AssistantMessage 只保存文本/tool/错误。 | 没有 output schema、模型能力或已解析 JSON 字段。 |
| 模型目录 | models_dev.py 已把 models.dev 的 structured_output 规范化并缓存。 | Model 没有对应字段,Pydantic 构造时丢弃;模型列表也没有展示该能力。 |
| Agent loop | AgentLoopConfig 继承公共 stream options;每轮可执行 tool,直到得到不含 tool call 的 assistant message。 | 重建 SimpleStreamOptions 时无 schema 可转发;没有最终候选校验与修复状态。 |
| 流事件 | provider 事件覆盖 text/thinking/tool/done/error;dispatcher 再转换为 Web/CLI 的 stream_event。 | 无法表示“第 1 个候选无效,清空后进入第 2 次尝试”及“最终结构化值已校验”。 |
| CLI / Web / API | --print 最终打印字符串;WS chat_response.result.content 是字符串;POST /api/function/{name} 返回异步 ack。 | 请求不能携带 schema,成功结果没有 typed JSON,错误没有稳定 machine-readable code。 |
| Session title | 设计文档写明 {title:string} schema。 | agent/dispatcher/titles.py 实际提示“ONLY title text”,再按行、引号、前缀与长度清理。 |
| Record/replay | format v1 记录 model、context、options 及原始 provider 事件;replay 严格比较字段。 | 加入默认字段会让旧录制与新请求产生形状差异;结构化校验重试需保持 call index 顺序。 |
调用与返回链
schema 必须沿这条现有链路传递;不新增绕过 Agent loop 的“structured provider client”,否则 tool calling、deadline、record/replay、DAG 与 session persistence 会形成第二套语义。
3. 公共 API 与规范化类型
保留参数名 response_format,因为它已经公开并出现在现有设计文档。支持两种输入形状:裸 JSON Schema,以及带 OpenProgram 控制项的 envelope。规范化只发生一次。
JsonValue = None | bool | int | float | str | list["JsonValue"] | dict[str, "JsonValue"]
class JsonSchemaOutput(BaseModel):
type: Literal["json_schema"] = "json_schema"
schema: dict[str, Any] # 调用方原始 schema,禁止原地改写
name: str = "response" # ^[A-Za-z_][A-Za-z0-9_-]{0,63}$
description: str | None = None
strict: bool = True
fallback: Literal["auto", "none", "prompt"] = "auto"
max_validation_retries: Literal[0, 1] = 1
# 兼容现有调用:裸 dict 视为 schema 本身,而不是 provider payload。
Runtime.exec(content=..., response_format={"type":"object", ...}) -> JsonValue
Runtime.exec(content=..., response_format=JsonSchemaOutput(...)) -> JsonValue
Runtime.exec(content=..., response_format=None) -> str
- 用 overload 表达返回类型;普通文本调用仍返回
str。结构化调用成功时不返回 JSON 字符串。 async_exec、exec、AgentSession 和 dispatcher 使用同一个JsonSchemaOutput;不接受 provider 私有response_formatpayload。- 网络请求前调用
validator_for(schema).check_schema(schema)。无法识别的 meta-schema 或非法 schema 抛StructuredOutputSchemaError,不消耗 provider 请求。 - 在
pyproject.toml直接声明jsonschema>=4.23。当前 lock 中的传递依赖不能作为核心功能契约。
Provider 公共字段
class Model(BaseModel):
structured_output: bool | None = None # models.dev: true / false / unknown
class StreamOptions(BaseModel):
...
response_format: JsonSchemaOutput | None = None
class AssistantMessage(BaseModel):
...
structured_output: JsonValue | None = None
structured_output_mode: Literal["native", "tool", "prompt"] | None = None
structured_output_attempt: int | None = None
None 是未知能力,不等于 False。原始 JSON 文本继续保存在 assistant text content 中,便于历史、DAG、审计和 replay;程序调用返回 structured_output。
4. Capability negotiation
模型目录只回答“该模型是否宣称支持 structured output”;API adapter 还必须回答 payload 方言、streaming、与 tools 的组合约束。协商同时检查两者。
class StructuredOutputCapabilities(BaseModel):
native: Literal["supported", "unsupported", "unknown"]
dialect: Literal["openai_chat", "openai_responses", "anthropic",
"google", "bedrock"] | None
streaming: bool
with_tools: bool
schema_profile: str # provider 子集检查器名称
negotiate(model, api_provider, options, tools) -> StructuredOutputPlan
# plan.mode: native | tool | prompt
# plan.provider_schema: 经等价投影后的副本
# plan.original_schema: 本地最终验证使用
- schema 自身非法:立即失败。
- adapter 为该 API 明确支持、模型字段不是
False、schema 可无损投影、当前 tools 组合受支持:选择native。 fallback="auto"且 provider 支持 strict tool schema、schema 可无损投影、tool_choice未禁止/强制其他 tool:选择隐藏 tool。fallback="prompt":显式选择 prompt fallback;它只保证本地校验,不宣称 provider-level strict。- 否则抛
StructuredOutputUnsupportedError,请求发出前给出 provider、model、冲突能力和 fallback 原因。
openai-codex 和 gemini-subscription 可能借用官方模型条目,但它们使用不同后端。除非该 API adapter 有契约测试,不能因为 Model.structured_output=True 就发送原生字段。Provider 映射与首版边界
| API | 原生 payload | 首版策略 | 组合限制 |
|---|---|---|---|
openai-completions | response_format={type:"json_schema", json_schema:{name,description,strict,schema}} | native | 按兼容 profile 检查 tools/parallel;不为 community endpoint 假定完整 OpenAI 支持。 |
openai-responses | text.format={type:"json_schema", name,description,strict,schema} | native | 保留 refusal/incomplete 终止语义;能力不满足时转 hidden tool。 |
azure-openai-responses | Responses text.format | native | 部署模型和 API version 必须通过 adapter 探针/契约测试,不能只看模型名。 |
anthropic-messages | output_config.format={type:"json_schema", schema} | native | 与现有 output_config.effort 合并,禁止覆盖;refusal/max_tokens 仍需本地失败分类。 |
google-generative-ai | response_mime_type="application/json" + response_json_schema | native | 只接受官方支持子集;工具组合在契约测试通过前标为 with_tools=False。 |
bedrock-converse-stream | outputConfig.textFormat.structure.jsonSchema,其中 schema 是 JSON 字符串 | native | 模型支持名单与 citations 冲突由 adapter 判定;本地仍按原 schema 校验。 |
openai-codex | 无已验证的 ChatGPT backend 契约 | tool fallback | 原生为 unknown;不发送 OpenAI Responses 私有字段。 |
gemini-subscription | Cloud Code Assist request 内部有 generationConfig,但兼容性未验证 | tool fallback | 独立契约测试通过后才能打开原生能力。 |
外部契约依据:OpenAI Structured Outputs、Anthropic structured outputs、Google GenerateContent API、Amazon Bedrock structured outputs、Azure OpenAI structured outputs。这些接口会变化,adapter 契约测试是启用能力的必要条件。
5. 原生模式与 fallback
原生 schema
adapter 只负责把规范化配置映射到 provider payload。现有 providers/_schema 可复用遍历与方言识别,但只能执行语义等价变换,例如 key 命名或 JSON 字符串序列化。任何删除 constraint、放宽 additionalProperties、改写 union 等有损处理都返回具体路径并使原生方案不可用。最终验证永远使用调用方原始 schema。
隐藏 tool fallback
{
"name": "__openprogram_submit_json",
"description": "Submit the final response matching the required schema.",
"parameters": <provider_schema>,
"strict": true
}
- 隐藏 tool 与调用方 tools 一起发送。普通 tool call 照常执行并进入下一轮。
- 隐藏 tool 必须单独出现;其 arguments 是最终候选,不实际调用 tool,也不生成
ToolResultMessage。 - fallback round 设置
parallel_tool_calls=False。如果调用方显式要求 parallel、tool_choice="none"或强制另一个 tool,协商失败,不覆盖调用方设置。 - 模型输出普通文本而不调用隐藏 tool,归类为
missing_submission,可进入一次修复请求。
Prompt fallback
只在调用方明确设置 fallback="prompt" 时启用:把紧凑 schema 和“仅返回 JSON”要求加入系统层,并对最终文本执行相同解析/校验。它不标记为 strict,不在 auto 中静默使用。这样可以区分 provider-level 约束与纯本地检测。
6. 流式事件和增量文本
structured output 不做增量 schema 校验。每次尝试仍转发原始文本 delta,消费者可显示正在生成的 JSON;最终 text_end 后才解析。为避免无效尝试被提交为最终消息,增加两个框架事件,并给 text 事件增加可选 attempt 元数据。
EventTextStart/TextDelta/TextEnd.output_attempt: int | None = None
EventStructuredOutputRetry {
type: "structured_output_retry",
attempt: 1,
next_attempt: 2,
issues: [{code, path, schema_path, message}],
}
EventStructuredOutputEnd {
type: "structured_output_end",
attempt: 1 | 2,
mode: "native" | "tool" | "prompt",
value: JsonValue,
}
done 后严格解析并校验。成功则发 structured_output_end,随后发唯一一次 done;stream 直接 EOF 而没有 raw done/error 时按 incomplete 失败,不能用 partial message 合成成功。structured_output_retry。CLI/Web 清空候选缓冲区;Agent loop 发起下一次 provider stream。error,不发 done,不持久化无效 assistant final。provider 自己的 raw done 由 Agent loop 消费;对外只暴露结构化事务的最终 done。普通文本模式保持原事件序列。CLI/TUI/Web 对未知新事件必须继续安全忽略,保证版本滚动升级。
7. 严格解析、校验和有限重试
解析规则
- direct text 使用
json.loads(raw, parse_constant=reject_nonfinite);拒绝 Markdown fence、前后说明文字、尾随内容、NaN/Infinity。 - 隐藏 tool arguments 同样按完整 JSON 文本解析,不使用
utils/json_parse.py的 partial/forgiving parser。 - 使用
jsonschema.validators.validator_for(original_schema)创建 validator,并安装禁止外部资源读取的 registry;错误排序固定为 instance path、schema path、message。 - schema preflight 拒绝所有无法解析到当前 schema 内嵌 resource 的
$ref/$dynamicRef/$recursiveRef;fragment 引用和由内嵌$id定义的相对引用可保留,因此 Runtime、CLI、Web 和 replay 都不会因本地校验发起 DNS、HTTP 或文件读取。 - schema 的规范 JSON 上限为 1 MiB,容器节点上限 4096、容器边上限 8192、深度上限 100;超限在 provider 调用前稳定返回
invalid_schema。 - fragment-only 引用允许本地 JSON Pointer/anchor;直接引用链形成的自环或互环在 preflight 拒绝,避免 validator 递归溢出。由实例结构推进的有限递归 schema 保持可用。
- 错误传输最多保留 20 项,每项 message 最多 500 字符,避免将完整敏感输出复制到日志和 WS 错误。
重试状态
| 失败 | 是否消耗 validation retry | 处理 |
|---|---|---|
| schema definition 非法 | 否 | 请求前 StructuredOutputSchemaError。 |
| provider/模型不支持或配置冲突 | 否 | 请求前 StructuredOutputUnsupportedError。 |
| JSON syntax、schema violation、missing hidden submission | 是,最多 1 次 | 把无效 assistant 候选和确定性的 issue 列表追加到临时修复上下文,再请求一次。 |
| refusal、content filter、安全阻止 | 否 | StructuredOutputGenerationError(code="refusal")。 |
max_tokens/incomplete、context length | 否 | 保留 provider reason;调用方调整预算。框架不自行放大 token 上限。 |
| transport/rate limit/provider internal | 否 | 继续由现有 stream/exec retry 处理;不计入 validation retry,但每次 Runtime provider 调用都消耗共享的 max_retries 预算。 |
| 取消或 deadline | 否 | 立即传播,不进入修复。 |
共享调用预算
max_validation_retries 与 max_retries 是两个限制,不是两个可相乘的循环。前者限制首次候选失败后最多进行多少次语义修复;后者限制一次同步或异步 exec 在 Runtime 边界发起的 provider 调用总数,包含初次调用、语义修复调用和 transport failure 后的调用。只有 validation retry 尚未耗尽且共享调用预算仍有余额时,Runtime 才发起修复调用。
因此 Runtime 边界的 provider 调用数始终不超过 max_retries。例如 max_retries=1 时,无效首个候选直接返回 StructuredOutputValidationError;max_retries=2 且默认允许一次 validation retry 时,最多是初次调用加一次修复。修复调用发生 transport failure 时,该失败已经消费预算,外层不能重新获得完整预算。provider adapter 或 SDK 自己的 transport retry 仍受现有 deadline 和 transport 层配置约束,不由 validation retry 计数。
structured_output_retry.attempt 和 next_attempt 继续表示结构化候选序号,不复用为 transport attempt。事件只在 Runtime 确认还有共享调用预算、即将进行修复时发送;预算耗尽时不发送虚假的 retry 事件。
修复 prompt 只包含失败类型和有界 issue 列表,不重新解释 schema,也不调用另一个 LLM。修复上下文只存在于该次 exec;session history 和最终 DAG 只保留最终有效 assistant message,但 model-call 记录保留两次 provider call 供审计。
8. Tool calling 共存
| 情形 | 行为 |
|---|---|
| 原生 schema,无用户 tools | 直接应用原生 output format。 |
原生 schema + 用户 tools,adapter with_tools=True | 每个 agent round 都携带 schema;用户 tool 正常执行,最终无 tool round 的文本被校验。 |
| 原生 schema + tools,但组合未验证 | 若 auto 可使用 hidden tool,则改用 tool fallback;否则 preflight 失败。 |
| tool fallback + 用户 tools | 隐藏 submit tool 与用户 tools 合并;普通 tools 可多轮执行,submit tool 结束事务。 |
| 一次响应同时含普通 tool 与 submit tool | 拒绝该候选并进入有限修复;不执行普通 tool,避免在“final”之后产生副作用。 |
| 调用方强制特定 tool / 禁用 tools | 不覆写调用方意图;native 可用则 native,否则 unsupported。 |
结构化配置必须在 agent_loop._stream_assistant_response() 重建 SimpleStreamOptions 时明确复制。最终验证位于 Agent loop 的 no-tool terminal 边界,而不是各 provider parser 中重复实现。
9. CLI、WebSocket/API 与错误语义
Python API
value = runtime.exec(content=..., response_format=schema) # Python JsonValue
except StructuredOutputError as e:
e.code # invalid_schema | unsupported | invalid_json | validation_failed |
# missing_submission | refusal | incomplete
e.provider
e.model
e.attempts
e.issues # bounded machine-readable list
StructuredOutputError 归入现有 LLM error 边界,但不伪装成 transport error。retryable 表示“新 exec/new budget 是否可能成功”;validation retry 是否已用尽由 attempts 表示。
CLI
在现有 one-shot openprogram --print 增加 --json-schema PATH;首版不改变交互 REPL/TUI 输入方式。成功时 stdout 只写规范 JSON(UTF-8,非 pretty,末尾换行),退出码 0。错误写 stderr:用法/非法 schema 为 2,unsupported 为 3,生成或最终校验失败为 4,现有 provider/auth/cancel 语义不变。结构化结果以紧凑 JSON 字符串持久化到 session assistant content,同时附带 message metadata 中的 parsed value/mode。
WebSocket 与 HTTP 异步 API
// request: existing chat action and POST /api/function/{name} body
response_format?: JsonSchemaOutput | JSONSchema
// stream
{type:"chat_response", data:{type:"stream_event", event:{
type:"structured_output_retry", attempt:1, next_attempt:2, issues:[...]}}}
// success
{type:"chat_response", data:{type:"result", content:"{...}",
structured_output:<JsonValue>, structured_output_mode:"native", attempt:1}}
// terminal error
{type:"chat_response", data:{type:"error", code:"validation_failed",
content:"Structured output failed validation", attempts:2, issues:[...]}}
POST /api/function/{name}继续立即返回{session_id,msg_id}ack;结果仍从同一 WS/事件链送达,不改成同步 HTTP response。- Web chat 默认不发送 schema。收到 schema result 时以 canonical JSON 文本显示,typed value 留给 API consumer;retry 事件清空当前候选文本。
- server 的
json.dumps(..., default=str)不得承担 structured value 序列化;value 在验证后已属于 JSON data model,否则视为内部错误。
10. Record/replay 与持久化
RECORDING_FORMAT_VERSION从 1 升到 2。v2 request options 记录完整规范化response_format;旧 v1 明确拒绝,避免默认None字段造成含糊 mismatch。- recording 继续记录 provider 原始事件,不记录本地派生的 validation events。replay 返回相同原始事件后,Agent loop 确定性地重建 retry/end/error。
- 每次 validation repair 是新的 provider call 和新的
call_index。录制中必须出现两个 request/call_end;replay 少一个或多一个均为 mismatch。 - schema 在规范化后按递归 key 排序计算 SHA-256,hash 用于日志、DAG metadata 和错误关联;recording 仍保存 schema 全文以便严格比较。hash 不替代 schema。
- session assistant content 保存最终 canonical JSON;message metadata 保存 mode、attempt、schema hash。无效候选不进入 session message chain。
11. Session title 迁移
基础能力完成后,把 agent/dispatcher/titles.py 改为首个内部 consumer:
TITLE_SCHEMA = {
"type": "object",
"properties": {"title": {"type": "string", "minLength": 1, "maxLength": 80}},
"required": ["title"],
"additionalProperties": false,
}
result = runtime.exec(content=prompt, toolset="none", max_iterations=2,
response_format=JsonSchemaOutput(
schema=TITLE_SCHEMA, name="session_title", max_validation_retries=1,
))
title = result["title"]
保留 Unicode trim 和最终数据库长度防御;删除“ONLY title text”以及去引号、去 Title:、取首行等格式修复逻辑。title 生成仍是 best-effort,structured error 时保留 phase-1 placeholder,不影响主 turn。同步修正 session/name.md 与 feature matrix,使文档区分“已实现”和“设计”。
12. References 对比:采用、修改、拒绝
仓库 references/pi-ai 已有统一 stream option、provider-local payload builder、tool schema 和 supportsStrictMode,但 strict 仅针对 tool definition;没有通用 response schema、最终 JSON validator 或 structured retry。
| 决定 | 来源/现状 | 本设计处理 |
|---|---|---|
| 采用 | 共享 option/type 经所有 provider adapter 透传。 | JsonSchemaOutput 放在公共类型;payload 映射留在各 provider。 |
| 采用 | tool strict 是 provider/model compatibility,而不是调用方单方面承诺。 | hidden tool fallback 受 capability negotiation 和 schema profile 约束。 |
| 修改 | pi-ai 把 strict 能力放在 compat 字段,models.dev 已有 structured_output 数据。 | 模型用 tri-state 字段,API adapter 用独立 capability;两者联合决定。 |
| 修改 | 现有 _schema 可以为 provider 降级 tool schema。 | structured output 只允许无损投影;本地始终按原始 schema 验证。 |
| 拒绝 | 只依赖 provider strict,直接返回文本。 | 拒绝。refusal、截断、兼容 endpoint 和 provider bug 都要求本地 parse + validate。 |
| 拒绝 | 用 forgiving/partial JSON parser 修正最终输出。 | 拒绝。只接受单一完整 JSON document,失败进入有界语义修复。 |
| 拒绝 | 把每个 JSON delta 解析为增量对象并对外发布。 | 拒绝。schema 校验只在完整候选上定义;流式阶段只传候选文本和 attempt。 |
| 拒绝 | 另建独立 structured-output provider 调用路径。 | 拒绝。必须复用 Agent loop、tools、deadline、DAG 和 record/replay。 |
13. 精确修改文件
| 文件 | 修改 |
|---|---|
| openprogram/providers/types.py | 新增 JsonSchemaOutput、JsonValue、Model tri-state、StreamOptions 字段、AssistantMessage parsed 字段与 structured retry/end 事件。 |
| openprogram/providers/api_registry.py openprogram/providers/register.py | 为 API adapter 注册 structured capability;提供 unknown-safe 查询,不按模型名猜测。 |
| openprogram/providers/structured_output.py new | 输入规范化、schema preflight、协商、严格解析、本地校验、issue 限界、schema hash 和错误类型。 |
| openprogram/providers/_schema/* | 增加“无损/有损”报告接口和 provider schema profile;保留原 schema。 |
| openprogram/providers/sources/models_dev.py openprogram/providers/enabled_models.py openprogram/webui/_model_listing/listing.py | 保留、构造并展示 structured_output tri-state。 |
| openprogram/providers/openai_completions/openai_completions.py openprogram/providers/openai_responses/openai_responses.py openprogram/providers/azure_openai_responses/azure_openai_responses.py | 映射 Chat/Responses/Azure 原生 payload,并处理 response refusal/incomplete。 |
| openprogram/providers/anthropic/anthropic.py | 把 format 合并进现有 output_config,保留 adaptive-thinking effort。 |
| openprogram/providers/google/google.py openprogram/providers/google_gemini_cli/google_gemini_cli.py | Google 官方 adapter 映射原生字段;subscription adapter 首版显式 unknown/tool fallback。 |
| openprogram/providers/amazon_bedrock/amazon_bedrock.py openprogram/providers/openai_codex/openai_codex.py | Bedrock 映射 outputConfig;Codex adapter 首版显式 unknown/tool fallback。 |
| openprogram/agent/types.py openprogram/agent/agent_loop.py openprogram/agent/internals/_event_parsing.py | 透传 schema;实现 no-tool terminal 校验、hidden tool、一次修复、派生事件和最终 parsed message。 |
| openprogram/agentic_programming/runtime.py openprogram/providers/callable_model.py | 修复 provider 路径静默忽略;增加 overload/parsed return;对 callable model 应用同一最终校验。 |
| openprogram/providers/recording.py openprogram/providers/replay.py | format v2、规范化 option 比较、validation retry 多 call 测试;provider raw event 集合保持不变。 |
| openprogram/_cli_cmds/chat.py openprogram/cli.py openprogram/cli_chat.py openprogram/_cli_chat/turn.py | one-shot schema 参数、typed result、stdout/stderr/exit code、session persistence。 |
| openprogram/webui/ws_actions/chat.py openprogram/webui/routes/chat.py openprogram/webui/_execute/chat.py openprogram/webui/server.py | 请求校验、schema 透传、structured stream/result/error envelope。 |
| cli/src/ws/client.ts cli/src/screens/repl/wsHandlers/handleChatResponse.ts cli/src/screens/repl/wsHandlers/streamingHelpers.ts | TS 请求/事件类型、retry 清空候选、typed result 接收;不增加 schema 编辑 UI。 |
| openprogram/agent/dispatcher/titles.py | 迁移 title consumer,删除文本格式清理,保留长度与 best-effort 边界。 |
| pyproject.toml uv.lock | 直接依赖 jsonschema 并更新 lock。 |
| docs/reference/design/runtime/session/name.md docs/reference/design/feature-matrix.html | 实现落地后修正文档状态和实际契约。 |
14. 测试矩阵与验收条件
| 层 | 必须覆盖 | 建议文件 |
|---|---|---|
| 规范化/validator | 裸 schema、envelope、非法 meta-schema、object/array/scalar/null、非有限数、fence/尾随文本、稳定 issue path;remote ref 零网络、本地直接引用环、1 MiB/4096 nodes/8192 edges/100 depth 全局预算。 | tests/providers/test_structured_output.py(new) |
| 协商 | true/false/unknown × adapter support;有损 schema;fallback none/auto/prompt;tool_choice/parallel 冲突。 | 同上 + test_provider_meta.py |
| Provider payload | 逐 adapter 快照测试;Anthropic effort+format 合并;Bedrock schema JSON 字符串;unknown adapter 不发送私有字段。 | 现有 tests/providers/test_openai.py、test_anthropic.py、test_gemini.py + 新 Bedrock cases |
| Streaming | 单次成功;invalid attempt 的 delta→retry→清空→成功;第 2 次失败无 done;取消/deadline 不重试;provider EOF 无 raw terminal 时不合成成功。 | tests/providers/test_structured_output_streaming.py(new) |
| Tools | 原生+tools;hidden tool 单独提交;普通 tool 多轮后提交;普通+submit 同轮拒绝;forced/none/parallel 冲突。 | tests/unit/test_tools_runtime.py + new Agent loop cases |
| Runtime | 无 schema 返回 str;有 schema 返回 JsonValue;默认 provider 路径确实透传;callable model 同样本地校验;同步/异步均证明初次调用、修复调用和 transport retry 共用 max_retries 预算。 | tests/agentic_programming/test_runtime_structured_output.py(new) |
| Record/replay | v2 单 call、validation retry 两 calls、schema mismatch path、v1 拒绝、离线派生事件一致。 | tests/providers/test_record_replay.py |
| CLI | 成功 stdout 仅 JSON;exit 2/3/4;stderr 无候选全文;普通 --print 不变。 | tests/providers/test_cli.py + CLI integration |
| WS/API | request schema、retry event、typed result、machine error、foreign session routing、旧客户端忽略新字段。 | tests/unit/test_webui_chat_dispatcher.py + TS tests |
| Session title | 合法 title、structured failure 回退 placeholder、Unicode/80 字边界、无旧正则依赖。 | tests/unit/test_dispatcher_compaction_title.py |
| 回归 | 全部 provider stream、tool loop、普通聊天、DAG、session persistence、typecheck、docs HTML。 | 现有 suites + npm test/npm run typecheck |
max_retries,且语义修复调用数不超过 max_validation_retries;普通文本模式的录制、事件和返回类型保持不变。15. 实施顺序与完成判定
- 公共类型、直接依赖、schema preflight/validator/错误类;先建立纯单元测试。
- capability registry、models.dev tri-state 保留、provider schema profiles。
- 各 provider payload 映射与 adapter 契约测试;未验证 adapter 保持 unknown。
- Agent loop 的终态校验、hidden tool、事件、一次修复;Runtime parsed return。
- record/replay v2、CLI、WS/API consumer;验证旧文本调用不变。
- 迁移 session title,修正 feature matrix 与 session naming 文档。
provider 能力标记、契约测试与 session title consumer 已落地。feature matrix 保持用户批准的 78.5 分和既有状态单元;完整回归门禁与具体 trust-boundary 证据仍在本页独立记录。单纯增加 provider payload 字段不构成功能完成。
16. 实现与验证证据
| 范围 | 已审查实现 | 边界 |
|---|---|---|
| 公共合同与 provider | JsonSchemaOutput 规范化和 preflight;registry snapshot 下的 native / hidden tool / explicit prompt 协商;OpenAI、Anthropic、Google 与 Bedrock 已验证映射;最终值按调用方原 schema 本地校验。 | 未验证 adapter、模型未显式 opt-in、schema 无法无损投影或工具控制冲突时不发送未经验证的 native payload;fallback="prompt" 必须显式请求。 |
| Agent / Runtime | Agent loop 统一拥有协商、typed retry/end 事件、最多一次 validation repair 与 parsed terminal;Runtime.exec/async_exec 返回已校验 Python JSON 值,普通调用仍返回文本。 | 无效候选不写入最终 assistant message;取消、deadline、refusal 与 incomplete 不转成 repair。 |
| 入口与复现 | CLI --json-schema PATH|-、Web typed lifecycle、recording v2 与严格离线 replay 共用规范化合同;CLI structured one-shot stdout 只有一个 JSON document。 | 没有 Web schema editor、跨语言代码生成或增量 JSON AST;v1 structured recording 明确拒绝,v1 ordinary recording 保持兼容。 |
| Session title | 默认 agent provider/model、toolset="none"、两次最大模型迭代和一次 schema repair;只接受 {title:string},consumer 仅做 Unicode trim 与 80 code-point 存储防御。 | setup、exec、close 失败均为 best-effort,并只记录固定生命周期标签;provider 异常正文和 traceback 不进入日志。 |
提交证据:Task 1 00945c85..27a5aa8a;Task 2 da4ececd..8ca7b04c;Task 3 6f8c80b7..546c2bc7;Task 4 27e4764f..510f60ee;Task 5 9743f3e1..86ca66d5;Task 6 5bba3e64..2fb471b3。Tasks 1–6 的最终 SPEC 与 QUALITY scoped review 均为 PASS、remaining 0。
验证状态:Task 6 affected selection 为 219 passed, 1 skipped;Task 7 focused selection 为 226 passed, 1 skipped;文档构建为 453 pages、链接检查为 0 broken。Task 7 最终树上的完整 Python selection 得到 3290 passed, 5 skipped, 1 xfailed, 2 failed;两个失败分别是既有 tool cancellation 的 ExecInterrupt 断言与 memory writer turn-order 断言,并已在实现前基线 86ca66d5 独立复现。它们不归因于 structured-output diff,也不满足“完整门禁通过”的发布条件。
后续质量修复:全局 schema preflight 已增加 1 MiB/4096 nodes/8192 edges/100 depth 预算,拒绝外部 resource 和直接引用环;local validator 使用禁止检索的 registry;structured stream 只有收到 provider raw terminal 才能提交。该修复的 scoped SPEC/QUALITY 结论在 review 完成后记录。
仓库证据:openprogram/providers/types.py、sources/models_dev.py、七类 provider adapter、agent/agent_loop.py、agentic_programming/runtime.py、agent/internals/_event_parsing.py、CLI/WebSocket 链、recording.py/replay.py、agent/dispatcher/titles.py、references/pi-ai。本文行号不绑定源码快照;实现时以符号和测试为定位依据。
外部 provider 文档仅用于确认当前 payload 与约束;能力启用仍以 OpenProgram adapter 契约测试为准。