feat: SillyTavern parity Phase 1(多 API 路由 / 宏引擎接入 / 推理内容展示) - #3
Merged
Merged
Conversation
- 抽象 ChatCompletionProvider 接口,统一流式调用 - OpenAICompatibleProvider:支持 OpenAI/DeepSeek/OpenRouter - AnthropicProvider:支持 Claude 3.5 Sonnet/Opus - GoogleGeminiProvider:支持 Gemini 1.5 Pro/Flash - ProviderFactory:根据配置创建对应 Provider - 自动识别推理内容(DeepSeek R1 reasoning_content) - 为后续接入多种 API 打好基础
- 消息表新增 reasoning_content 字段 - Database schema 升级到 v9 - ReasoningContent 模型类 - 为 DeepSeek R1 等模型的思考过程提供存储 - ChatCompletionProvider 已支持推理内容识别 - UI 显示待后续实现
- MacroEngine:支持 {{char}}/{{user}}/{{random}}/{{roll}}/{{maxc}}/{{minc}}
- PromptBuilder:组装角色卡、用户人设、聊天历史
- 自动替换角色卡和消息中的宏
- 支持随机选择和掷骰子
- 为完整 Prompt 组装打好基础
- 后续扩展:Author's Note、示例对话、世界书注入、Token 预算
- GreetingSelector:支持多个备用开场白选择和随机
- ExampleDialogueParser:解析 <START> 分隔的示例对话
- PromptBuilder 集成示例对话到系统 Prompt
- 支持 {{user}}/{{char}} 宏在示例对话中替换
- alternate_greetings 和 mes_example 字段已完整利用
- 为角色卡导入的完整性打好基础
check Phase 1 时发现 llm/、prompt/、util/GreetingSelector 这批代码全是孤立
的——没有任何地方 import 它们,App 实际发消息走的还是
OpenAiCompatibleClient 原来的路径,宏替换/示例对话/推理内容/备用开场白
全部不生效。本次把其中三项真正接进这条真实链路:
- MacroEngine:接进 OpenAiCompatibleClient 的 system prompt 组装(单聊+
群聊)和每条历史消息内容,{{user}} 取自 PersonaRepository 的默认
persona 名字(在已有的后台线程里解析,不阻塞主线程)。
- ExampleDialogueParser:接进单聊 system prompt;顺带修了一个真实 bug——
parseBlock 原来每个 <START> 块只保留最后一组 user/char 行,多轮示例对话
会静默丢掉前面几轮。
- GreetingSelector:接进 ChatRepository.loadSession 的首条消息——有备用
开场白的角色新开聊天时随机选一条,没有备用开场白的角色行为完全不变。
补充 MacroEngineTest / ExampleDialogueParserTest / GreetingSelectorTest,
覆盖修复前会漏掉的多轮示例场景。
Anthropic/Google Gemini 的真实多 provider 路由和推理内容的存储+展示还没
接(这两块要动 StreamAccumulator/StreamSession/DB 读写/UI,风险和范围都
大过这次,需要单独一轮做)。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
之前 llm/ 下的 AnthropicProvider/GoogleGeminiProvider/ProviderFactory 是 孤立代码,没有任何地方调用。本次把这两个 provider 接进真实发消息的 OpenAiCompatibleClient: - ProviderCatalog 新增 anthropic/google 两个预设,设置页的 provider 下拉 框零改动就能选到(原本就是从 ProviderCatalog.getPresets() 读的)。 - OpenAiCompatibleClient.executeStream 按 providerId 分流:Anthropic/ Google 走 ChatCompletionProvider 路径,其余(OpenAI/DeepSeek/ OpenRouter/自定义端点,本来就是 OpenAI 兼容格式)继续走原来的 /chat/completions 路径,不变。System prompt 组装(世界书+宏+示例对话+ 长期记忆)完全复用,和请求格式无关。 - ChatCompletionProvider 接口加 connectionSink 参数,让 Anthropic/Google 的 HttpURLConnection 能像原路径一样被 StreamCall.cancel() 拿到并 disconnect(),停止按钮对这两个 provider 同样有效。 - AnthropicProvider/GoogleGeminiProvider 的 base URL 从写死的常量改成走 用户在设置里配置的 baseUrl(默认官方地址),和其它 provider 一样支持 自建代理。 - 图片附件消息:这两个 provider 的多模态格式和 OpenAI 不同,本轮不做, 遇到带图片的消息直接给出"当前所选模型暂不支持图片附件"的明确错误, 而不是发一个格式错误的请求。 - ModelConnectionTester 的"测试连接"按钮同步适配这两个 provider 的鉴权 方式(Anthropic 用 x-api-key + anthropic-version,Google 的 key 拼进 查询串),否则用户选中它们点测试会得到误导性的错误信息。 补充 ProviderCatalogTest / OpenAiCompatibleClientTest 覆盖新预设匹配和 provider 路由分流逻辑。 推理内容(DeepSeek/Claude 思考过程)的存储与展示是方案 B,还没做——这次 的 StreamCallback.onContent(delta, isReasoning) 里 isReasoning 内容先被 丢弃。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
messages.reasoning_content 列从 v9 迁移起就在,但没人写也没人读。这次接 通整条链路,同时刻意不碰 StreamAccumulator/StreamSession——那是这个项目 里状态机不变量最严格的部分(RT-1~RT-5 全套"只终态一次/只持久化一次"测 试覆盖),推理内容走一条独立于它们之外的并行侧信道,零风险改动那部分: - SseEventParser.Event 新增 reasoningDelta,解析 delta.reasoning_content (DeepSeek R1 风格)。 - AnthropicProvider 新增 thinking_delta 解析,Claude 的 extended thinking 现在也能被捕获(通过 StreamCallback.onContent(text, true), 即方案 A 里已经搭好、之前被丢弃的 isReasoning 分支)。 - OpenAiCompatibleClient.StreamListener 新增 onReasoningDelta 回调, OpenAI 兼容格式和 Anthropic/Google 原生格式两条路径都会触发。 - ChatViewModel 用一个独立于 StreamSession 之外的 StringBuilder 侧信道 累积推理内容,在 onTerminalText 持久化时读一次快照——完全不改 StreamAccumulator/StreamSession 的状态转换逻辑。 - ChatMessage/ChatHistoryStore/ChatRepository 补上 reasoningContent 的 读写:新增字段用现有"最全参数构造函数 + 旧构造函数委托默认值"的模式, addMessage/addGroupMessage 各加一个带 reasoningContent 的重载。只有新 生成的回复会记录推理内容——重 roll(appendMessageVersion)不单独为每 个版本存一份,只保留首次生成的那份。 - item_message.xml/MessageAdapter 加一个默认折叠的"🤔 思考过程"区块, 点开才展开,避免长 CoT 把气泡撑爆;只在消息已持久化(reasoningContent 非空)后才显示——流式过程中的实时增量不接入这条 UI(推理内容不进 StreamAccumulator,只在终态时落库)。 补充 SseEventParserTest 覆盖 reasoning_content 解析。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SillyTavern feature-parity Phase 1:多 API 真实路由、宏引擎/示例对话/备用开场白接入真实发消息链路、推理内容存储与展示。
ProviderCatalog新增 Anthropic / Google Gemini 预设,OpenAiCompatibleClient按 providerId 分流到各自真实请求格式(其余 OpenAI 兼容 provider 继续走原路径不变);连接测试、取消请求同步适配。{{char}}/{{user}}/{{random}}/{{roll}})、示例对话(mes_example)、备用开场白全部接进真实发消息链路(此前是完全孤立、未被任何代码引用的死代码)。reasoning_content/ Claude extended thinking):完整存储 + UI 折叠展示,走独立于StreamAccumulator/StreamSession之外的并行侧信道,不改动那套状态机的行为。ExampleDialogueParser原来每个<START>块只保留最后一组 user/char 行,多轮示例对话会静默丢弃前面几轮。Test plan
./gradlew testDebugUnitTest lintDebug assembleDebug全过,新增 30+ 个单元测试覆盖宏引擎/示例对话解析/开场白选择/provider 路由/SSE 推理内容解析🤖 Generated with Claude Code