背景
当前 NeoCode 采用事件流架构,Provider 通过 StreamEvent 向 Runtime 推送流式事件。这种设计允许 TUI 实时展示模型推理过程,同时为未来的完全流式化改造(去掉 ChatResponse 返回值)奠定基础。
在审查事件流的完整性时,发现两个问题:
- Provider 内部仍保留已废弃的辅助函数
- 事件流存在信息缺口,导致 Runtime 无法从事件重建完整消息
之前存在的问题
问题 1:mergeToolCallDeltas 函数已废弃但仍保留
该函数在之前的重构中已被 consumeStream 内联处理替代,不再被任何代码调用,但仍保留在代码库中:
- 占用约 27 行代码
- 相关测试
TestMergeToolCallDeltas 和 TestMergeToolCallDeltasEdgeCases 占用约 150 行
- 可能误导后续维护者
问题 2:事件流信息缺口,无法关联 tool_call_start 和 tool_call_delta
现象:
// tool_call_start 事件(原先)
StreamEvent{
Type: StreamEventToolCallStart,
ToolCallID: "call_123",
ToolName: "edit_file",
ToolCallIndex: -1, // ❌ 强制为 -1,丢失了真实 index
}
// tool_call_delta 事件
StreamEvent{
Type: StreamEventToolCallDelta,
ToolCallIndex: 0,
ToolArgumentsDelta: "{\"path\":\"a.go\"}",
// ❌ 没有 ToolCallID
}
问题:
tool_call_start 有 ID 和 name,但缺少 index
tool_call_delta 有 index 和 arguments,但缺少 ID
- 两个事件无法通过 index 关联
影响:
如果未来要删除 ChatResponse 返回值,让 Runtime 完全从事件流重建 Message:
// Runtime 需要自己累积事件
accumulator := newStreamAccumulator()
for event := range events {
accumulator.consume(event)
}
message := accumulator.buildMessage() // ❌ 无法正确重建
Runtime 无法知道某个 tool_call_delta 属于哪个 tool call,因为:
- 创建
toolCalls map 条目时(tool_call_start)不知道 index
- 追加 arguments 时(
tool_call_delta)需要用 index 查找对应条目
问题 3:注释与实现不一致
StreamEvent.ToolCallIndex 的注释声明:
ToolCallIndex int `json:"tool_call_index"` // 非 tool_call_delta 时必须强制赋值为 -1
这个设计基于错误假设:"只有 tool_call_delta 需要 index"。实际上 tool_call_start 也需要 index 来定位 tool call 的位置。
预期解决方式
解决方案 1:删除废弃代码
- 删除
mergeToolCallDeltas 函数
- 删除
TestMergeToolCallDeltas 测试
- 删除
TestMergeToolCallDeltasEdgeCases 测试
- 更新相关注释
解决方案 2:修复事件流信息缺口
修改 emitToolCallStart 函数签名,添加 index 参数:
// 修改前
func emitToolCallStart(ctx context.Context, events chan<- StreamEvent, id, name string) error
// 修改后
func emitToolCallStart(ctx context.Context, events chan<- StreamEvent, index int, id, name string) error
修复后的事件流:
// tool_call_start 事件(修复后)
StreamEvent{
Type: StreamEventToolCallStart,
ToolCallIndex: 0, // ✅ 有真实 index
ToolCallID: "call_123",
ToolName: "edit_file",
}
// tool_call_delta 事件
StreamEvent{
Type: StreamEventToolCallDelta,
ToolCallIndex: 0, // ✅ 可以通过 index 关联
ToolArgumentsDelta: "{\"path\":\"a.go\"}",
}
解决方案 3:优化事件结构
重新组织 StreamEvent 字段,按事件类型分组并更新注释:
type StreamEvent struct {
Type StreamEventType
// text_delta
Text string `json:"text,omitempty"`
// tool_call_start / tool_call_delta
ToolCallIndex int `json:"tool_call_index,omitempty"` // 工具调用索引
ToolCallID string `json:"tool_call_id,omitempty"` // 工具调用 ID
ToolName string `json:"tool_name,omitempty"` // 工具名称
ToolArgumentsDelta string `json:"tool_arguments_delta,omitempty"` // 参数增量
// message_done
FinishReason string `json:"finish_reason,omitempty"`
Usage *Usage `json:"usage,omitempty"`
}
同时清理不必要的字段设置:
emitTextDelta 不再设置 ToolCallIndex
emitMessageDone 不再设置 ToolCallIndex
背景
当前 NeoCode 采用事件流架构,Provider 通过
StreamEvent向 Runtime 推送流式事件。这种设计允许 TUI 实时展示模型推理过程,同时为未来的完全流式化改造(去掉ChatResponse返回值)奠定基础。在审查事件流的完整性时,发现两个问题:
之前存在的问题
问题 1:
mergeToolCallDeltas函数已废弃但仍保留该函数在之前的重构中已被
consumeStream内联处理替代,不再被任何代码调用,但仍保留在代码库中:TestMergeToolCallDeltas和TestMergeToolCallDeltasEdgeCases占用约 150 行问题 2:事件流信息缺口,无法关联 tool_call_start 和 tool_call_delta
现象:
问题:
tool_call_start有 ID 和 name,但缺少 indextool_call_delta有 index 和 arguments,但缺少 ID影响:
如果未来要删除
ChatResponse返回值,让 Runtime 完全从事件流重建Message:Runtime 无法知道某个
tool_call_delta属于哪个 tool call,因为:toolCallsmap 条目时(tool_call_start)不知道 indextool_call_delta)需要用 index 查找对应条目问题 3:注释与实现不一致
StreamEvent.ToolCallIndex的注释声明:这个设计基于错误假设:"只有 tool_call_delta 需要 index"。实际上
tool_call_start也需要 index 来定位 tool call 的位置。预期解决方式
解决方案 1:删除废弃代码
mergeToolCallDeltas函数TestMergeToolCallDeltas测试TestMergeToolCallDeltasEdgeCases测试解决方案 2:修复事件流信息缺口
修改
emitToolCallStart函数签名,添加index参数:修复后的事件流:
解决方案 3:优化事件结构
重新组织
StreamEvent字段,按事件类型分组并更新注释:同时清理不必要的字段设置:
emitTextDelta不再设置ToolCallIndexemitMessageDone不再设置ToolCallIndex