[文档验收]:完成全双工Agent文档和最终门禁,包含安全策略、验收命令和OpenSpec校验

This commit is contained in:
mkbk
2026-06-18 22:32:23 +08:00
parent 6541bab143
commit e730883c64
8 changed files with 103 additions and 11 deletions
+1
View File
@@ -2,6 +2,7 @@ OWNER_LLM_BASE_URL=https://token-plan-cn.xiaomimimo.com/v1
OWNER_LLM_API_KEY=
OWNER_LLM_MODEL=mimo-v2.5
OWNER_LLM_API_STYLE=chat_completions
OWNER_LLM_STREAMING_ENABLED=1
OWNER_ASSISTANT_MODE=turn_based_voice_pet
OWNER_REALTIME_TRANSCRIPT_ENABLED=1
OWNER_REALTIME_TRANSCRIPT_IDLE_TIMEOUT_MS=1500
+1
View File
@@ -19,3 +19,4 @@ dist/
*.egg-info/
logs/
tmp/
openinterpreter/
+15 -1
View File
@@ -37,6 +37,7 @@ OWNER_LLM_BASE_URL=https://token-plan-cn.xiaomimimo.com/v1
OWNER_LLM_API_KEY=
OWNER_LLM_MODEL=mimo-v2.5
OWNER_LLM_API_STYLE=chat_completions
OWNER_LLM_STREAMING_ENABLED=1
OWNER_ASSISTANT_MODE=turn_based_voice_pet
OWNER_REALTIME_TRANSCRIPT_ENABLED=1
OWNER_REALTIME_TRANSCRIPT_IDLE_TIMEOUT_MS=1500
@@ -130,7 +131,18 @@ OWNER_COMPUTER_CONTROL_ENABLED=0
这个命令会把有效运行模式固定为 `full_duplex_agent` 并输出全双工配置摘要,但当前不会启动未接线的全双工运行时。真实语音交互仍继续使用 `run-live`。后续实现会在这个入口下逐步接入 WebRTC APM、持续监听、Streaming STT/TTS、长期记忆和 Tool Router。
全双工 Agent 相关配置默认只做规划和安全关闭:`OWNER_AUDIO_APM_PROVIDER=webrtc` 代表目标音频底座,`OWNER_STREAMING_STT_PROVIDER=faster_whisper``OWNER_STREAMING_TTS_PROVIDER=cosyvoice` 是后续 provider 目标;`OWNER_MEMORY_ENABLED=0``OWNER_TOOL_ROUTER_ENABLED=0``OWNER_OPENINTERPRETER_ENABLED=0``OWNER_BROWSER_PLAYWRIGHT_ENABLED=0``OWNER_COMPUTER_CONTROL_ENABLED=0` 默认关闭,避免尚未完成安全边界前执行长期记忆或工具任务。
全双工 Agent 相关配置默认只做规划和安全关闭:`OWNER_AUDIO_APM_PROVIDER=webrtc` 代表目标音频底座,`OWNER_LLM_STREAMING_ENABLED=1` 控制 LLM 以流式响应供后续句子级 TTS 消费,旧变量 `OWNER_LLM_STREAM` 仍兼容;`OWNER_STREAMING_STT_PROVIDER=faster_whisper``OWNER_STREAMING_TTS_PROVIDER=cosyvoice` 是后续 provider 目标;`OWNER_MEMORY_ENABLED=0``OWNER_TOOL_ROUTER_ENABLED=0``OWNER_OPENINTERPRETER_ENABLED=0``OWNER_BROWSER_PLAYWRIGHT_ENABLED=0``OWNER_COMPUTER_CONTROL_ENABLED=0` 默认关闭,避免尚未完成安全边界前执行长期记忆或工具任务。
当前已落地的全双工基础模块:
- `full_duplex_audio`:音频帧 fixture、capture/render ring buffer、fake WebRTC APM、APM 探针和 fallback 决策。
- `full_duplex_control`:全双工状态机、事件诊断字段、取消 token graph 和恢复协调器。
- `full_duplex_speech`VAD provider contract、Silero VAD 边界、Streaming STT contract、fake STT 和 interruption detector。
- `full_duplex_response`LLM streaming contract、句子切分、TTS 文本净化、fake Streaming TTS 和可中断播放队列。
- `agent_memory`SQLite memory schema、FAISS index manifest 校验、fake/disabled memory manager、敏感写入策略和 memory recall 注入。
- `tool_router`:结构化工具调用、风险分类、预算防循环、审计脱敏、`memory.search``memory.save``shell.readonly`
- `external_adapters`Open Interpreter、Playwright 和 Computer Control 的默认关闭边界。
- `full_duplex_testing`fake 全双工 fixture、性能指标和诊断脱敏测试工具。
可选依赖按能力分组,不会被默认安装强制拉入:
@@ -141,6 +153,8 @@ OWNER_COMPUTER_CONTROL_ENABLED=0
.venv/bin/python -m pip install -e '.[browser]'
```
工具执行安全策略见 `docs/full_duplex_agent_security.md`
## 本地模型
首次运行前必须准备本地语音模型;同一脚本会下载 wake、VAD、2025 中文 CTC STT 和 GTCRN denoiser 模型:
+47
View File
@@ -0,0 +1,47 @@
# 全双工 Agent 安全确认策略
## 默认原则
全双工 Agent 的工具执行默认最小权限。语音输入可能被误触发,工具请求必须经过结构化校验、风险分类、预算限制、输出脱敏和审计记录。
## 自动允许
以下请求可在第一版策略中自动执行:
- `memory.search`:只读长期记忆检索。
- `shell.readonly`:允许目录内的只读命令,例如 `pwd``ls``find``rg``cat``sed``git status``git diff``git log``git show`
- 低风险 dry-run adapter 检查,例如 `run-agent-live --check-config` 这类不改变外部状态的诊断。
## 需要确认
以下请求必须进入确认流程:
- `memory.save` 保存疑似敏感内容。
- `openinterpreter.run` 中包含写文件、删除、上传、账号、权限、安装依赖、执行宽泛代码的任务。
- `browser.playwright` 中包含登录、支付、购买、提交表单、私信、上传、账号修改或不可逆动作的任务。
- 任何自然语言意图中出现删除、上传、支付、购买、交易、账号、权限等高风险关键词的工具调用。
## 默认拒绝
以下请求第一版直接拒绝:
- `shell.readonly` 中出现 `rm``chmod``chown``pip install``npm install``brew install`、重定向写入、`mv``cp` 等修改外部状态的命令。
- 未注册工具名、未知参数结构或超过单轮工具预算的请求。
- 直接 GUI 点击、键盘输入、屏幕控制或 macOS Accessibility 自动化请求。
- 试图绕过确认策略、读取或输出密钥、上传本地敏感文件的请求。
## 审计和脱敏
工具审计记录只保存工具名、风险级别、确认状态、耗时、状态和脱敏摘要。API key、Authorization header、原始音频、PCM payload、密码和 token 形态文本必须被替换为 `[redacted]` 或被截断。
## Open Interpreter 边界
本地 `openinterpreter/` 是外部候选后端,不复制、不 vendoring、不提交到 Owner。Owner 后续只通过 `OWNER_OPENINTERPRETER_COMMAND` 指定的 CLI/子进程调用它,并由 Tool Router 负责目录限制、超时、输出截断和确认策略。
## 浏览器边界
`browser.playwright` 默认关闭,默认使用隔离 browser context。是否允许复用用户当前 Chrome 登录态需要后续人工确认;第一版不默认复用登录态。
## Computer Control 边界
Codex Computer Use 只作为安全确认策略参考,不复制私有实现。后续 GUI 控制必须另开 OpenSpec,基于公共能力实现,例如 macOS Accessibility、Playwright 或 trycua-style provider。
@@ -91,12 +91,12 @@
## 10. 文档、验收与模块提交
- [ ] 10.1 更新 README 全双工架构说明;前置条件:入口和配置确认;优先级:P1;验收标准:说明 APM、Streaming STT/TTS、记忆、工具、限制;测试要点:命令示例和默认关闭项准确。
- [ ] 10.2 更新 `.env.example` 规划变量;前置条件:配置清单冻结;优先级:P1;验收标准:新增全双工、APM、memory、tool、adapter 配置;测试要点:默认值不启用高风险工具。
- [ ] 10.3 编写模型和依赖准备说明;前置条件:provider 候选确认;优先级:P1;验收标准:区分开发默认和产品候选;测试要点:不承诺未验证模型已可用。
- [ ] 10.4 编写安全确认策略文档;前置条件:Tool Router 风险规则完成;优先级:P0;验收标准:列明自动允许、需要确认、默认拒绝类别;测试要点:与工具测试用例一致。
- [ ] 10.5 执行 Python 编译验证;前置条件:实现代码变更完成;优先级:P0;验收标准:`.venv/bin/python -m compileall src tests scripts` 通过;测试要点:无语法错误。
- [ ] 10.6 执行单元测试验证;前置条件:测试实现完成;优先级:P0;验收标准:`.venv/bin/python -m unittest discover -s tests` 通过;测试要点:覆盖 full-duplex、memory、tool、security。
- [ ] 10.7 执行安全和模型检查;前置条件:相关命令实现完成;优先级:P0;验收标准:security-check、model-check、device-check 按阶段通过或给出明确跳过理由;测试要点:不泄露密钥。
- [ ] 10.8 执行 OpenSpec 校验;前置条件:docs 和 spec 更新完成;优先级:P0;验收标准:`openspec validate add-full-duplex-agent-voice-assistant --strict``openspec validate --all --strict` 通过;测试要点:无 spec 格式错误。
- [ ] 10.9 按模块提交;前置条件:对应模块验证通过;优先级:P0;验收标准:提交信息符合 `[模块名]:完成[具体功能描述],包含[关键变更]`;测试要点:`git status --short` 不含本模块未提交改动。
- [x] 10.1 更新 README 全双工架构说明;前置条件:入口和配置确认;优先级:P1;验收标准:说明 APM、Streaming STT/TTS、记忆、工具、限制;测试要点:命令示例和默认关闭项准确。
- [x] 10.2 更新 `.env.example` 规划变量;前置条件:配置清单冻结;优先级:P1;验收标准:新增全双工、APM、memory、tool、adapter 配置;测试要点:默认值不启用高风险工具。
- [x] 10.3 编写模型和依赖准备说明;前置条件:provider 候选确认;优先级:P1;验收标准:区分开发默认和产品候选;测试要点:不承诺未验证模型已可用。
- [x] 10.4 编写安全确认策略文档;前置条件:Tool Router 风险规则完成;优先级:P0;验收标准:列明自动允许、需要确认、默认拒绝类别;测试要点:与工具测试用例一致。
- [x] 10.5 执行 Python 编译验证;前置条件:实现代码变更完成;优先级:P0;验收标准:`.venv/bin/python -m compileall src tests scripts` 通过;测试要点:无语法错误。
- [x] 10.6 执行单元测试验证;前置条件:测试实现完成;优先级:P0;验收标准:`.venv/bin/python -m unittest discover -s tests` 通过;测试要点:覆盖 full-duplex、memory、tool、security。
- [x] 10.7 执行安全和模型检查;前置条件:相关命令实现完成;优先级:P0;验收标准:security-check、model-check、device-check 按阶段通过或给出明确跳过理由;测试要点:不泄露密钥。
- [x] 10.8 执行 OpenSpec 校验;前置条件:docs 和 spec 更新完成;优先级:P0;验收标准:`openspec validate add-full-duplex-agent-voice-assistant --strict``openspec validate --all --strict` 通过;测试要点:无 spec 格式错误。
- [x] 10.9 按模块提交;前置条件:对应模块验证通过;优先级:P0;验收标准:提交信息符合 `[模块名]:完成[具体功能描述],包含[关键变更]`;测试要点:`git status --short` 不含本模块未提交改动。
+1
View File
@@ -223,6 +223,7 @@ def main(argv: list[str] | None = None) -> int:
"audio_apm_provider": config.audio_apm_provider,
"streaming_stt_provider": config.streaming_stt_provider,
"streaming_tts_provider": config.streaming_tts_provider,
"llm_streaming_enabled": config.llm_stream,
"memory_provider": config.memory_provider,
"memory_enabled": config.memory_enabled,
"tool_router_enabled": config.tool_router_enabled,
+7 -1
View File
@@ -104,6 +104,12 @@ class AppConfig:
def get_bool(name: str, default: str = "0") -> bool:
return (get(name, default) or default).lower() not in {"0", "false", "no"}
def get_bool_compat(name: str, legacy_name: str, default: str = "0") -> bool:
value = get(name)
if value is None:
value = get(legacy_name, default)
return (value or default).lower() not in {"0", "false", "no"}
return cls(
assistant_mode=(
get("ASSISTANT_MODE", "turn_based_voice_pet") or "turn_based_voice_pet"
@@ -115,7 +121,7 @@ class AppConfig:
llm_api_key=get("LLM_API_KEY"),
llm_model=get("LLM_MODEL", "mimo-v2.5") or "mimo-v2.5",
llm_api_style=get("LLM_API_STYLE", "chat_completions") or "chat_completions",
llm_stream=(get("LLM_STREAM", "1") or "1").lower() not in {"0", "false", "no"},
llm_stream=get_bool_compat("LLM_STREAMING_ENABLED", "LLM_STREAM", "1"),
realtime_transcript_enabled=(get("REALTIME_TRANSCRIPT_ENABLED", "1") or "1").lower()
not in {"0", "false", "no"},
realtime_transcript_idle_timeout_ms=int(
+22
View File
@@ -166,6 +166,7 @@ class ModelsConfigTests(unittest.TestCase):
"OWNER_AUDIO_RING_BUFFER_MS=1200",
"OWNER_INTERRUPT_ENABLED=0",
"OWNER_INTERRUPT_TARGET_LATENCY_MS=180",
"OWNER_LLM_STREAMING_ENABLED=0",
"OWNER_STREAMING_STT_PROVIDER=sherpa_onnx",
"OWNER_STREAMING_STT_PRODUCT_CANDIDATE=faster_whisper",
"OWNER_STREAMING_TTS_PROVIDER=macos_say",
@@ -196,6 +197,7 @@ class ModelsConfigTests(unittest.TestCase):
self.assertEqual(config.audio_ring_buffer_ms, 1200)
self.assertFalse(config.interrupt_enabled)
self.assertEqual(config.interrupt_target_latency_ms, 180)
self.assertFalse(config.llm_stream)
self.assertEqual(config.streaming_stt_provider, "sherpa_onnx")
self.assertEqual(config.streaming_stt_product_candidate, "faster_whisper")
self.assertEqual(config.streaming_tts_provider, "macos_say")
@@ -212,6 +214,26 @@ class ModelsConfigTests(unittest.TestCase):
self.assertTrue(config.computer_control_enabled)
self.assertEqual(config.validate_basic(), [])
def test_llm_streaming_enabled_takes_precedence_over_legacy_llm_stream(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
path = f"{tmp}/.env"
with open(path, "w", encoding="utf-8") as handle:
handle.write("OWNER_LLM_STREAM=0\nOWNER_LLM_STREAMING_ENABLED=1\n")
config = AppConfig.from_dotenv(path)
self.assertTrue(config.llm_stream)
def test_legacy_llm_stream_still_disables_streaming(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
path = f"{tmp}/.env"
with open(path, "w", encoding="utf-8") as handle:
handle.write("OWNER_LLM_STREAM=0\n")
config = AppConfig.from_dotenv(path)
self.assertFalse(config.llm_stream)
def test_speech_provider_must_be_cloud_or_local(self) -> None:
config = AppConfig(speech_provider="invalid")
errors = config.validate_basic()