TauriTavern/docs/Agent/PromptAssembly.md

157 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Agent Prompt Assembly
本文档记录 2026-05-27 当前已落地的 Agent Profile 独立 Preset、独立 Model 与前后端提示词组装链路。历史推演见 `docs/PromptAssemblyPlan.md`;当前开发定位以本文为准。
## 核心原则
- 真实提示词组装仍由前端 SillyTavern `PromptManager` 完成,Rust 不重写近似版 prompt builder。
- Rust 负责解析 `ResolvedAgentProfile`、加载 preset、解析 LLM Connection,并生成前端 broker request。
- `FrozenRunInputSnapshot` 是本次 run 的输入事实;broker 只能从其中读取 `promptInputs`、`worldInfoActivation`、`macroContext`。
- `preset.ref` 是 prompt compiler input,不是对现有 `promptSnapshot` 的字符串补丁。
- `model.connectionRef + modelId` 与 preset provider source 解耦;preset 不拥有 endpoint、secret、最终 source/model。
- 错误必须 fail-fast,不允许静默回退 Legacy Generate 或当前 UI preset/model。
## Profile 绑定语义
当前 Profile 相关字段:
```json
{
"preset": {
"mode": "ref",
"ref": { "apiId": "openai", "name": "Writer Preset" }
},
"model": {
"mode": "connectionRef",
"connectionRef": "deepseek-main",
"modelId": "deepseek-chat-v4-flash"
}
}
```
`preset.mode`:
- `currentPromptSnapshot` / `none`:兼容路径,使用当前前端生成出的 prompt snapshot。
- `ref`:加载指定 OpenAI/chat-completion preset,并走 Frontend PromptAssemblyBroker。
`model.mode`:
- `currentPromptSnapshot`:沿用 prompt snapshot 中已有 source/model。
- `connectionRef`:通过 `LlmConnectionService` 解析 connection 与 `modelId`,覆盖组装 settings 与最终 runtime payload。
独立 preset 当前只支持 OpenAI/chat-completion preset,因为真实组装入口复用 `src/scripts/openai.js` 的 PromptManager 链路。
## FrozenRunInputSnapshot
前端在 Agent Mode 的 Legacy Generate 准备阶段冻结:
```json
{
"schemaVersion": 1,
"kind": "tauritavern.agentFrozenRunInputSnapshot",
"generationType": "normal",
"promptInputs": {},
"worldInfoActivation": {},
"macroContext": {}
}
```
来源:
- `promptInputs`:本次 Generate 已计算出的角色、世界书文本、extension prompts、bias、messages、messageExamples 等 PromptManager 输入。
- `worldInfoActivation`:本次 run 激活的世界书事实,用于 `worldinfo.read_activated` 与审计。
- `macroContext`:冻结 `{{user}}`、`{{char}}`、角色字段、persona、示例消息、`{{model}}` 等宏所需上下文。
extension prompts 在冻结时会先执行 filter,只保留非空、结构化、可 clone 的 `value / position / depth / scan / role`。
## 当前生产链路
```text
用户点击发送
-> sendTextareaMessage()
-> getAgentGenerationOptions()
-> Generate(..., agentMode=true)
-> OpenAI 分支构造 promptInputs
-> buildFrozenRunInputSnapshot()
-> prepare_agent_prompt_assembly(dto)
-> PromptAssemblyService 加载 preset.ref
-> PromptAssemblyService 应用 model.connectionRef + modelId 到 settings
-> 返回 frontendPromptAssembly broker request
-> frontend buildPromptAssemblySnapshot(request)
-> headless PromptManager 真实组装 messages
-> createGenerationParameters(settings, model, ...)
-> 得到 promptSnapshot.chatCompletionPayload
-> start_agent_run(dto)
-> AgentRuntimeService 提取 ChatCompletionGenerateRequestDto
-> runtime 再次 apply_connection_to_payload()
-> prepare_agent_tool_request() 注入 Agent tools
-> AgentModelGateway -> ChatCompletionService -> provider request
```
注意:Rust 不是主动回调前端。实现上是前端先调用 `prepare_agent_prompt_assembly`,Rust 返回 broker request,前端再本地调用 broker 组装并提交 `start_agent_run`。
## Broker Request
Rust 返回的 `frontendPromptAssembly` request 包含:
```text
schemaVersion
kind = tauritavern.agentPromptAssemblyRequest
profileId
generationType
frozenRunInputSnapshot
settings
modelId?
presetRef
agentContextPolicy
agentSystemPrompt
jsonSchema?
fingerprint { presetSha256, frozenRunInputSnapshotSha256 }
```
`settings` 是“preset settings + model binding overlay”后的有效 settings。broker 不允许额外接收顶层 `promptInputs`、`worldInfoActivation`、`macroContext`,防止冻结输入事实分叉。
## Model 解耦与双阶段覆盖
第一阶段发生在 `PromptAssemblyService`:
1. 加载 `preset.ref` 的原始 preset settings。
2. 若 `model.mode = connectionRef`,解析 connection 和 `modelId`。
3. 删除 preset 中 connection-owned fields:`chat_completion_source`、各 source model key、`custom_url`、`secret_id`、reverse proxy、source-specific endpoint 等。
4. 写入 broker 组装所需的 `chat_completion_source` 和对应 source model key,例如 DeepSeek 写 `deepseek_model`。
第二阶段发生在 Agent runtime:
1. `start_agent_run` 收到 broker 产出的 `promptSnapshot.chatCompletionPayload`。
2. executor 在 tool loop 前调用 `apply_connection_to_payload(connectionRef, modelId, payload)`。
3. 最终 payload 的 source、model、endpoint、secret、reverse proxy、adapter hints 以 LLM Connection 为权威。
这两个阶段都需要存在:前端组装阶段需要正确 source/model 计算 PromptManager 与 generation parameters;runtime 阶段需要保证真正发送请求时不受 preset 中旧连接信息污染。
## 关键文件
- `src/script.js`:Agent 发送入口、Legacy Generate 准备、`FrozenRunInputSnapshot` 创建。
- `src/scripts/tauritavern/agent/frozen-run-input-snapshot.js`:冻结输入结构与 normalization。
- `src/tauri/main/api/agent-prompt-assembly-run.js`:prepare/buildSnapshot 编排。
- `src/tauri/main/api/agent-prompt-assembly.js`:Frontend PromptAssemblyBroker。
- `src/scripts/openai.js`:headless PromptManager、settings normalization、真实 OpenAI/chat-completion prompt assembly。
- `src-tauri/src/application/services/prompt_assembly_service.rs`:Rust PromptAssemblyService、preset 加载、broker request、组装阶段 model overlay。
- `src-tauri/src/application/services/llm_connection_service.rs`:runtime payload 连接覆盖。
- `src-tauri/src/application/services/agent_runtime_service/lifecycle.rs`:`start_agent_run` 输入校验与 run 创建。
- `src-tauri/src/application/services/agent_runtime_service/executor.rs`:runtime model binding、tool request 准备。
- `src-tauri/src/application/services/agent_model_gateway/`:Agent canonical IR 与 ChatCompletion DTO 转换。
## 兼容边界
- 不切换当前 UI preset,不修改 global `oai_settings`,不污染当前前端模型选择。
- headless broker 默认不触发普通 `CHAT_COMPLETION_PROMPT_READY`;依赖该事件改写 prompt 的扩展不会参与 Agent 独立组装。
- 动态 extension prompts 在 frozen snapshot 创建时已经物化;broker 组装阶段不会重新执行动态 filter。
- Agent runtime 拒绝外部 `tools`、`tool_choice`、已有 `role: "tool"` 或 assistant `tool_calls`。
- 当前 return-mode child invocation 已有后端 task prompt、target Profile system prompt 与 model binding;但完整 runtime-time PromptAssemblyBroker handshake 尚未落地。root run 的独立 preset/model 是已落地基础,subagent/handoff 还需要 invocation-scoped broker handshake。
## 后续开发注意
- 不要把独立 preset 实现为临时切换 UI preset 后 dryRun。
- 不要在 Rust 中手写简化 prompt builder 来替代 PromptManager。
- 新增 provider source 时,需要同步 `PromptAssemblyService::prompt_model_setting_key` 与 LLM connection payload overlay。
- 后续 subagent/handoff 应复用 `FrozenRunInputSnapshot` 和 broker contract,但需要为 `AgentInvocation` 增加 invocation-scoped prompt snapshot、provider_state 与 task/handoff packet。