文档中心 / SDK 教程 / 进阶篇

人机协同

在真实产品里,你往往不希望在用户不知情时直接删除文件覆盖写入或执行高危命令。Cody 把人机协同做成一等公民:模型可以通过 question 工具向你追问上下文,也可以在命中 CONFIRM 级别的内置工具(如 exec_commandwrite_fileedit_file 等)时暂停,等你用 submit_interaction() 表态后再继续。本篇沿用系列约定:模型依赖环境变量中的 qwen3.5-plus,不显式写死在代码里。

若尚未配置,可在 shell 中导出(与第 01 篇一致):CODY_MODEL=qwen3.5-plusCODY_MODEL_API_KEYCODY_MODEL_BASE_URL=https://coding.dashscope.aliyuncs.com/v1。需要写死时可用 Cody().model("qwen3.5-plus").base_url(...).api_key(...)

完整示例:流式里接住每一次暂停

下面是一段可直接改编进应用的骨架:async for 遍历 client.stream(),遇到 chunk.type == "interaction_request" 就读取 request_idinteraction_kindcontentoptions,再调用 await client.submit_interaction(...) 把人的选择送回 runner。其余 chunk 类型(例如 text_delta)按你的 UI 处理即可。

import asyncio
from cody.sdk import Cody
from cody.core.errors import InteractionTimeoutError

async def main():
    # 开启人机协同:超时秒数内必须 submit,否则抛 InteractionTimeoutError
    client = (
        Cody()
        .workdir(".")
        .interaction(enabled=True, timeout=30)
        .build()
    )
    async with client:
        try:
            async for chunk in client.stream("在改代码前先问我是否要备份"):
                if chunk.type == "interaction_request":
                    # 与 submit_interaction 成对:request_id 必填
                    rid = chunk.request_id
                    kind = chunk.interaction_kind
                    text = chunk.content
                    opts = chunk.options
                    print(f"[人机] {kind}: {text}", flush=True)
                    if opts:
                        print("选项:", opts, flush=True)
                    # 演示:按 kind 分支(真实产品里接 UI / CLI 输入)
                    if kind == "question":
                        await client.submit_interaction(
                            request_id=rid,
                            action="answer",
                            content="先备份再修改",
                        )
                    elif kind == "confirm":
                        await client.submit_interaction(
                            request_id=rid,
                            action="approve",
                            content="",
                        )
                    else:
                        # feedback:结构化批复(approve / reject / revise)
                        await client.submit_interaction(
                            request_id=rid,
                            action="revise",
                            content="请改用 pathlib 处理路径",
                        )
                elif chunk.type == "text_delta":
                    print(chunk.content, end="", flush=True)
        except InteractionTimeoutError:
            print("\n人机交互超时,运行已终止")

asyncio.run(main())

两种触发方式

流里出现的 interaction_request,背后对应两类业务场景:

开启与默认行为

在 Builder 上链接 .interaction(enabled=True, timeout=30) 即可。其中 timeout 表示:从请求推送到流中开始,若在这么多秒内没有匹配的 submit_interaction,运行会以 InteractionTimeoutError 结束。

默认 interaction(enabled=False):CONFIRM 级工具会自动批准,不会暂停,也不会给你处理 interaction_request 的机会。只有显式打开后,人机闭环才生效。

interaction_kind 三种场景对照

核心类型在引擎里统一为 "question" | "confirm" | "feedback"。下表归纳常见含义与你在 submit_interaction 里常用的 action(完整取值以 SDK 为准:"answer" | "approve" | "reject" | "revise")。

interaction_kind 典型场景 content / options 常用 action
question 模型通过 question 工具追问或让你选方案 content 为问题正文;options 为候选列表或 None answer(把用户文字放进 content);也可用 approve / reject 让工具侧返回简短标记
confirm CONFIRM 工具(如写文件、执行命令)执行前的闸门 多为「工具名 + 参数摘要」的提示文案 approve 放行;reject 拒绝执行(会按权限错误处理)
feedback 框架预留的「结构化批复」通道,与 approve/reject/revise 语义对齐 依具体调用方填充的提示与上下文 approverejectreviserevise 时常把修改意见写在 content

submit_interaction 参数

异步客户端方法签名为 await client.submit_interaction(request_id, action="answer", content="")。三个参数分工如下:

参数说明
request_id必须与当前 interaction_request chunk 上的 chunk.request_id 一致,用于把响应挂回正确的挂起请求。
actionanswer(回答问题)、approve(批准)、reject(拒绝)、revise(要求修订并常配合 content)。
content用户可见的答复或说明;例如 question 场景下传入完整回答,纯确认场景常留空字符串。

处理完一次 interaction_request 后,流会继续推进;同一轮任务里可能出现多次暂停,因此分支里务必每次使用当次 chunk 提供的 request_id,不要缓存错配。

超时:InteractionTimeoutError

若在 interaction(..., timeout=30) 指定的时间内未调用 submit_interaction 完成响应,异步流会终止并抛出 InteractionTimeoutError(从 cody.core.errors 导入)。建议在 async for 外层用 try/except 捕获,向用户提示「等待输入超时」并做清理或重试。

from cody.core.errors import InteractionTimeoutError

try:
    async for chunk in client.stream("任务"):
        ...
except InteractionTimeoutError:
    # 超时后运行已终止,在此记录日志或更新 UI
    ...

同步 CodyClient 的限制

同步包装类 CodyClient 与底层 run_sync 路径不会为你保留「边跑边等人」的交互语义:相关请求会被自动批准,无法像异步 stream() 那样在事件间隙调用 submit_interaction。要实现人机闭环,请使用 AsyncCodyClientasync for client.stream(...)

小结

你已经学会用 .interaction(enabled=True, timeout=...) 打开人机协同,在流式 chunk 中识别 interaction_request,按 question / confirm / feedback 选择 action,并通过 submit_interaction 关闭等待环;也了解了默认关闭时的自动批准行为、超时异常,以及同步客户端不适用该模式的原因。下一篇「存储抽象」将介绍如何把会话与产物接到你自己的存储后端。

← 上一篇 项目记忆