package service import ( "strings" "oci-portal/internal/aiwire" ) // RespEvent 是一条 Responses SSE 语义事件(event 名 + data 载荷)。 type RespEvent struct { Event string Data map[string]any } // RespStream 把 IR chunk 流聚合为 Responses 语义事件序列。 // 与 AnthStream 同构,但终态事件须携带完整 Response 快照,故全程缓冲文本与实参。 type RespStream struct { id string model string created int64 seq int started bool items []aiwire.RespOutItem msgOpen bool text strings.Builder tool aiwire.RespOutItem toolIdx int tOpen bool args strings.Builder finish string usage *aiwire.Usage } // NewRespStream 构造状态机;id 形如 resp_*,created 为响应时间戳。 func NewRespStream(id, model string, created int64) *RespStream { return &RespStream{id: id, model: model, created: created} } // Feed 消化一个上游 chunk,返回应立即下发的事件。 func (s *RespStream) Feed(chunk aiwire.ChatChunk) []RespEvent { var evs []RespEvent if !s.started { s.started = true evs = append(evs, s.respEvent("response.created", "in_progress"), s.respEvent("response.in_progress", "in_progress")) } if chunk.Usage != nil { s.usage = chunk.Usage } if len(chunk.Choices) == 0 { return evs } choice := chunk.Choices[0] if choice.Delta.Content != "" { evs = append(evs, s.feedText(choice.Delta.Content)...) } for _, tc := range choice.Delta.ToolCalls { evs = append(evs, s.feedTool(tc)...) } if choice.FinishReason != nil { s.finish = *choice.FinishReason } return evs } // Finish 关闭未闭合的块并产出终态事件(带完整 Response 快照)。 func (s *RespStream) Finish() []RespEvent { var evs []RespEvent if !s.started { s.started = true evs = append(evs, s.respEvent("response.created", "in_progress")) } evs = append(evs, s.closeMsg()...) evs = append(evs, s.closeTool()...) status := "completed" if s.finish == "length" { status = "incomplete" } return append(evs, s.respEvent("response."+status, status)) } // Usage 返回聚合到的用量(可能为 nil),供调用日志。 func (s *RespStream) Usage() *aiwire.Usage { return s.usage } // ev 构造带自增 sequence_number 的事件。 func (s *RespStream) ev(typ string, kv map[string]any) RespEvent { s.seq++ data := map[string]any{"type": typ, "sequence_number": s.seq} for k, v := range kv { data[k] = v } return RespEvent{Event: typ, Data: data} } // respEvent 构造携带 Response 快照的生命周期事件。 func (s *RespStream) respEvent(typ, status string) RespEvent { return s.ev(typ, map[string]any{"response": s.snapshot(status)}) } // snapshot 组装当前累计状态的 Response 对象。 func (s *RespStream) snapshot(status string) aiwire.Response { resp := aiwire.Response{ID: s.id, Object: "response", CreatedAt: s.created, Status: status, Model: s.model, Store: false, Output: append([]aiwire.RespOutItem{}, s.items...)} resp.Usage = respUsage(s.usage) if status == "incomplete" { resp.IncompleteDetails = &aiwire.RespIncomplete{Reason: "max_output_tokens"} } return resp } // feedText 处理文本增量:必要时开 message item 与 content part。 func (s *RespStream) feedText(delta string) []RespEvent { evs := s.closeTool() if !s.msgOpen { s.msgOpen = true s.text.Reset() item := aiwire.RespOutItem{Type: "message", ID: s.itemID("msg"), Status: "in_progress", Role: "assistant", Content: []aiwire.RespOutPart{}} evs = append(evs, s.ev("response.output_item.added", map[string]any{ "output_index": len(s.items), "item": item})) evs = append(evs, s.ev("response.content_part.added", map[string]any{ "item_id": item.ID, "output_index": len(s.items), "content_index": 0, "part": aiwire.RespOutPart{Type: "output_text", Text: "", Annotations: []any{}}})) } s.text.WriteString(delta) return append(evs, s.ev("response.output_text.delta", map[string]any{ "item_id": s.itemID("msg"), "output_index": len(s.items), "content_index": 0, "delta": delta})) } // closeMsg 闭合当前 message item(text done → part done → item done)。 func (s *RespStream) closeMsg() []RespEvent { if !s.msgOpen { return nil } s.msgOpen = false id, idx, full := s.itemID("msg"), len(s.items), s.text.String() part := aiwire.RespOutPart{Type: "output_text", Text: full, Annotations: []any{}} item := aiwire.RespOutItem{Type: "message", ID: id, Status: "completed", Role: "assistant", Content: []aiwire.RespOutPart{part}} evs := []RespEvent{ s.ev("response.output_text.done", map[string]any{"item_id": id, "output_index": idx, "content_index": 0, "text": full}), s.ev("response.content_part.done", map[string]any{"item_id": id, "output_index": idx, "content_index": 0, "part": part}), s.ev("response.output_item.done", map[string]any{"output_index": idx, "item": item}), } s.items = append(s.items, item) return evs } // feedTool 处理工具调用增量:新 Index 先闭合旧调用再开新 item。 func (s *RespStream) feedTool(tc aiwire.ToolCallDelta) []RespEvent { evs := s.closeMsg() if s.tOpen && tc.Index != s.toolIdx { evs = append(evs, s.closeTool()...) } if !s.tOpen { s.tOpen, s.toolIdx = true, tc.Index s.args.Reset() s.tool = aiwire.RespOutItem{Type: "function_call", ID: s.itemID("fc"), Status: "in_progress", CallID: tc.ID, Name: tc.Function.Name} evs = append(evs, s.ev("response.output_item.added", map[string]any{ "output_index": len(s.items), "item": s.tool})) } if tc.ID != "" && s.tool.CallID == "" { s.tool.CallID = tc.ID } if tc.Function.Name != "" && s.tool.Name == "" { s.tool.Name = tc.Function.Name } if tc.Function.Arguments == "" { return evs } s.args.WriteString(tc.Function.Arguments) return append(evs, s.ev("response.function_call_arguments.delta", map[string]any{ "item_id": s.tool.ID, "output_index": len(s.items), "delta": tc.Function.Arguments})) } // closeTool 闭合当前 function_call item(arguments done → item done)。 func (s *RespStream) closeTool() []RespEvent { if !s.tOpen { return nil } s.tOpen = false s.tool.Arguments = respArgs(s.args.String()) s.tool.Status = "completed" idx := len(s.items) evs := []RespEvent{ s.ev("response.function_call_arguments.done", map[string]any{ "item_id": s.tool.ID, "output_index": idx, "arguments": s.tool.Arguments}), s.ev("response.output_item.done", map[string]any{"output_index": idx, "item": s.tool}), } s.items = append(s.items, s.tool) return evs } // itemID 按当前 output 序号派生确定性 item ID。 func (s *RespStream) itemID(kind string) string { return respItemID(s.id, kind, len(s.items)) }