Version: 0.9.53.dev.260429

后端:
1. 流式思考链路从 raw reasoning_content 切到 `thinking_summary` 摘要协议,补齐摘要 prompt、digestor 与 Lite 压缩链路,plan / execute / fallback 统一改为“只出摘要、不透原始推理”,正文开始后自动关停摘要流。
2. thinking_summary 打通 timeline / SSE / outbox 持久化闭环,只落 detail_summary 与必要 metadata,并补强 seq 自检、冲突幂等识别与补 seq 回填,提升重放恢复稳定性。
3. 会话历史口径继续收紧,assistant 正文与时间线不再回写 raw reasoning_content,仅保留正文与思考耗时,避免刷新恢复时再次暴露内部推理文本。

前端:
4. 助手页开始接入 thinking_summary 实时流与历史恢复,补齐短摘要状态、长摘要折叠区、正文开流后自动收口,并增加调试入口用于协议联调与验收。
5. 当前前端助手页仍是残次过渡态,本版先以 thinking_summary 协议接通和基础渲染为主,样式、交互与细节体验暂未收平,下一版集中修复。

仓库:
6. 补充 thinking_summary 对接说明,明确 SSE 协议、timeline 恢复口径与 short/detail summary 的使用边界。
This commit is contained in:
Losita
2026-04-29 01:00:38 +08:00
parent d89e2830a9
commit f81f137791
21 changed files with 8566 additions and 229 deletions

View File

@@ -56,6 +56,15 @@ func collectExecuteDecisionFromLLM(
parser := newagentrouter.NewStreamDecisionParser()
output := &executeDecisionStreamOutput{firstChunk: true}
var fullText strings.Builder
reasoningDigestor, digestorErr := emitter.NewReasoningDigestor(ctx, executeSpeakBlockID, executeStageName)
if digestorErr != nil {
return nil, fmt.Errorf("执行 thinking 摘要器初始化失败: %w", digestorErr)
}
defer func() {
if reasoningDigestor != nil {
_ = reasoningDigestor.Close(ctx)
}
}()
for {
chunk, recvErr := reader.Recv()
@@ -68,15 +77,9 @@ func collectExecuteDecisionFromLLM(
}
if chunk != nil && strings.TrimSpace(chunk.ReasoningContent) != "" {
if emitErr := emitter.EmitReasoningText(
executeSpeakBlockID,
executeStageName,
chunk.ReasoningContent,
output.firstChunk,
); emitErr != nil {
return nil, fmt.Errorf("执行 thinking 推送失败: %w", emitErr)
if reasoningDigestor != nil {
reasoningDigestor.Append(chunk.ReasoningContent)
}
output.firstChunk = false
}
content := ""
@@ -148,6 +151,9 @@ func collectExecuteDecisionFromLLM(
output.decision = decision
if visible != "" {
if reasoningDigestor != nil {
reasoningDigestor.MarkContentStarted()
}
if emitErr := emitter.EmitAssistantText(
executeSpeakBlockID,
executeStageName,
@@ -174,9 +180,14 @@ func collectExecuteDecisionFromLLM(
continue
}
if strings.TrimSpace(chunk2.ReasoningContent) != "" {
_ = emitter.EmitReasoningText(executeSpeakBlockID, executeStageName, chunk2.ReasoningContent, false)
if reasoningDigestor != nil {
reasoningDigestor.Append(chunk2.ReasoningContent)
}
}
if chunk2.Content != "" {
if reasoningDigestor != nil {
reasoningDigestor.MarkContentStarted()
}
if emitErr := emitter.EmitAssistantText(
executeSpeakBlockID,
executeStageName,

View File

@@ -106,6 +106,15 @@ func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
parser := newagentrouter.NewStreamDecisionParser()
firstChunk := true
speakStreamed := false
reasoningDigestor, digestorErr := emitter.NewReasoningDigestor(ctx, planSpeakBlockID, planStageName)
if digestorErr != nil {
return fmt.Errorf("规划 thinking 摘要器初始化失败: %w", digestorErr)
}
defer func() {
if reasoningDigestor != nil {
_ = reasoningDigestor.Close(ctx)
}
}()
// 3.1 阶段一:解析决策标签。
for {
@@ -118,12 +127,11 @@ func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
break
}
// thinking 内容独立推流
// thinking 内容只进入摘要器,不再把 raw reasoning_content 透传给前端
if chunk != nil && strings.TrimSpace(chunk.ReasoningContent) != "" {
if emitErr := emitter.EmitReasoningText(planSpeakBlockID, planStageName, chunk.ReasoningContent, firstChunk); emitErr != nil {
return fmt.Errorf("规划 thinking 推送失败: %w", emitErr)
if reasoningDigestor != nil {
reasoningDigestor.Append(chunk.ReasoningContent)
}
firstChunk = false
}
content := ""
@@ -152,6 +160,9 @@ func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
// 3.2 阶段二:流式推送 speak同一 reader 继续读取)。
var fullText strings.Builder
if visible != "" {
if reasoningDigestor != nil {
reasoningDigestor.MarkContentStarted()
}
if emitErr := emitter.EmitAssistantText(planSpeakBlockID, planStageName, visible, firstChunk); emitErr != nil {
return fmt.Errorf("规划文案推送失败: %w", emitErr)
}
@@ -172,9 +183,14 @@ func RunPlanNode(ctx context.Context, input PlanNodeInput) error {
continue
}
if strings.TrimSpace(chunk2.ReasoningContent) != "" {
_ = emitter.EmitReasoningText(planSpeakBlockID, planStageName, chunk2.ReasoningContent, false)
if reasoningDigestor != nil {
reasoningDigestor.Append(chunk2.ReasoningContent)
}
}
if chunk2.Content != "" {
if reasoningDigestor != nil {
reasoningDigestor.MarkContentStarted()
}
if emitErr := emitter.EmitAssistantText(planSpeakBlockID, planStageName, chunk2.Content, firstChunk); emitErr != nil {
return fmt.Errorf("规划文案推送失败: %w", emitErr)
}