修复 AI 网关流式断流:错误透传、自动降级,max_tokens 可缺省
This commit is contained in:
@@ -258,7 +258,7 @@ type AnthEvent struct {
|
||||
|
||||
// AnthRespBridge 把直通 SSE 事件流桥接为 Anthropic 事件序列:
|
||||
// message_start → content_block_start/delta/stop(text 与 tool_use 分块)→ message_delta → message_stop。
|
||||
// reasoning 系列事件丢弃。
|
||||
// reasoning 系列事件丢弃;上游 error / response.failed 转 Anthropic error 事件透传。
|
||||
type AnthRespBridge struct {
|
||||
id, model string
|
||||
started bool
|
||||
@@ -267,6 +267,11 @@ type AnthRespBridge struct {
|
||||
blockIndex int
|
||||
stopReason string
|
||||
usage aiwire.AnthUsage
|
||||
// sawTerminal 标记收到过终态事件(completed/incomplete/failed);
|
||||
// 上游流提前 EOF 时据此发 error 事件而非伪装正常结束
|
||||
sawTerminal bool
|
||||
// errMsg 记录上游错误消息,非空即本流已失败(供调用日志)
|
||||
errMsg string
|
||||
}
|
||||
|
||||
// NewAnthRespBridge 构造桥;id 为响应消息 ID。
|
||||
@@ -278,6 +283,7 @@ func NewAnthRespBridge(id, model string) *AnthRespBridge {
|
||||
type respStreamEvent struct {
|
||||
Type string `json:"type"`
|
||||
Delta string `json:"delta"`
|
||||
Message string `json:"message"`
|
||||
Item *respOutputItem `json:"item"`
|
||||
Response *respPayload `json:"response"`
|
||||
}
|
||||
@@ -288,27 +294,130 @@ func (st *AnthRespBridge) Feed(data []byte) []AnthEvent {
|
||||
if json.Unmarshal(data, &ev) != nil {
|
||||
return nil
|
||||
}
|
||||
var events []AnthEvent
|
||||
if !st.started {
|
||||
st.started = true
|
||||
events = append(events, st.startEvent())
|
||||
if ev.Type == "error" || ev.Type == "response.failed" {
|
||||
return st.failWith(ev)
|
||||
}
|
||||
// message_start 延迟到首个可见输出事件:created / reasoning 阶段不向客户端
|
||||
// 写任何字节,上游此段断流时 handler 才有降级非流式重做的无感窗口
|
||||
var events []AnthEvent
|
||||
switch ev.Type {
|
||||
case "response.output_item.added":
|
||||
if ev.Item != nil && ev.Item.Type == "function_call" {
|
||||
events = append(events, st.ensureStarted()...)
|
||||
events = append(events, st.openBlock(true, ev.Item.CallID, ev.Item.Name)...)
|
||||
st.stopReason = "tool_use"
|
||||
}
|
||||
case "response.output_text.delta":
|
||||
events = append(events, st.ensureStarted()...)
|
||||
events = append(events, st.textDelta(ev.Delta)...)
|
||||
case "response.function_call_arguments.delta":
|
||||
events = append(events, st.argsDelta(ev.Delta)...)
|
||||
case "response.completed", "response.incomplete", "response.failed":
|
||||
case "response.completed", "response.incomplete":
|
||||
st.sawTerminal = true
|
||||
st.finishFrom(ev.Response)
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// ensureStarted 在首个可见输出事件前补发 message_start(仅一次)。
|
||||
func (st *AnthRespBridge) ensureStarted() []AnthEvent {
|
||||
if st.started {
|
||||
return nil
|
||||
}
|
||||
st.started = true
|
||||
return []AnthEvent{st.startEvent()}
|
||||
}
|
||||
|
||||
// failWith 记录上游错误并产出 Anthropic error 事件(每流至多一次)。
|
||||
func (st *AnthRespBridge) failWith(ev respStreamEvent) []AnthEvent {
|
||||
if st.errMsg != "" {
|
||||
return nil
|
||||
}
|
||||
st.sawTerminal = true
|
||||
msg := ev.Message
|
||||
if ev.Type == "response.failed" {
|
||||
st.finishFrom(ev.Response)
|
||||
if m := respErrorMsg(ev.Response); m != "" {
|
||||
msg = m
|
||||
}
|
||||
}
|
||||
if msg == "" {
|
||||
msg = "上游返回错误事件 " + ev.Type
|
||||
}
|
||||
st.errMsg = msg
|
||||
return []AnthEvent{st.errorEvent()}
|
||||
}
|
||||
|
||||
// respErrorMsg 提取 response.failed 载荷中的错误消息。
|
||||
func respErrorMsg(resp *respPayload) string {
|
||||
if resp == nil || resp.Error == nil {
|
||||
return ""
|
||||
}
|
||||
return (*resp.Error)["message"]
|
||||
}
|
||||
|
||||
// errorEvent 按 Anthropic 流式协议产出 error 事件。
|
||||
func (st *AnthRespBridge) errorEvent() AnthEvent {
|
||||
return AnthEvent{Event: "error", Data: map[string]any{
|
||||
"type": "error",
|
||||
"error": map[string]string{"type": "api_error", "message": st.errMsg},
|
||||
}}
|
||||
}
|
||||
|
||||
// Err 返回上游错误消息;空串表示流正常(供调用日志)。
|
||||
func (st *AnthRespBridge) Err() string { return st.errMsg }
|
||||
|
||||
// SawTerminal 报告是否收到过终态事件;false 即上游流提前终止。
|
||||
func (st *AnthRespBridge) SawTerminal() bool { return st.sawTerminal }
|
||||
|
||||
// AnthMessageEvents 把完整 Messages 响应展开为标准事件序列,
|
||||
// 供流式上游断流后的非流式降级结果推送(客户端协议不变)。
|
||||
func AnthMessageEvents(m *aiwire.MessagesResponse) []AnthEvent {
|
||||
events := []AnthEvent{{Event: "message_start", Data: map[string]any{
|
||||
"type": "message_start",
|
||||
"message": map[string]any{
|
||||
"id": m.ID, "type": "message", "role": m.Role, "model": m.Model,
|
||||
"content": []any{}, "stop_reason": nil,
|
||||
"usage": map[string]int{"input_tokens": 0, "output_tokens": 0},
|
||||
},
|
||||
}}}
|
||||
for i, b := range m.Content {
|
||||
events = append(events, anthBlockEvents(i, b)...)
|
||||
}
|
||||
usage := map[string]int{"input_tokens": m.Usage.InputTokens, "output_tokens": m.Usage.OutputTokens}
|
||||
if m.Usage.CacheReadInputTokens > 0 {
|
||||
usage["cache_read_input_tokens"] = m.Usage.CacheReadInputTokens
|
||||
}
|
||||
events = append(events,
|
||||
AnthEvent{Event: "message_delta", Data: map[string]any{
|
||||
"type": "message_delta",
|
||||
"delta": map[string]any{"stop_reason": m.StopReason, "stop_sequence": nil},
|
||||
"usage": usage,
|
||||
}},
|
||||
AnthEvent{Event: "message_stop", Data: map[string]any{"type": "message_stop"}})
|
||||
return events
|
||||
}
|
||||
|
||||
// anthBlockEvents 把一个内容块展开为 start / delta / stop 三事件。
|
||||
func anthBlockEvents(index int, b aiwire.AnthBlock) []AnthEvent {
|
||||
var start, delta map[string]any
|
||||
if b.Type == "tool_use" {
|
||||
start = map[string]any{"type": "tool_use", "id": b.ID, "name": b.Name, "input": map[string]any{}}
|
||||
delta = map[string]any{"type": "input_json_delta", "partial_json": string(b.Input)}
|
||||
} else {
|
||||
start = map[string]any{"type": "text", "text": ""}
|
||||
delta = map[string]any{"type": "text_delta", "text": b.Text}
|
||||
}
|
||||
return []AnthEvent{
|
||||
{Event: "content_block_start", Data: map[string]any{
|
||||
"type": "content_block_start", "index": index, "content_block": start}},
|
||||
{Event: "content_block_delta", Data: map[string]any{
|
||||
"type": "content_block_delta", "index": index, "delta": delta}},
|
||||
{Event: "content_block_stop", Data: map[string]any{
|
||||
"type": "content_block_stop", "index": index}},
|
||||
}
|
||||
}
|
||||
|
||||
func (st *AnthRespBridge) textDelta(delta string) []AnthEvent {
|
||||
var events []AnthEvent
|
||||
if !st.blockOpen || st.blockIsTool {
|
||||
@@ -378,7 +487,16 @@ func (st *AnthRespBridge) closeBlockEvent() AnthEvent {
|
||||
}
|
||||
|
||||
// Finish 在上游流结束后收尾:关块 → message_delta(stop_reason+usage)→ message_stop。
|
||||
// 已发过 error 事件的流不再补终态;未见终态事件即 EOF 视为上游提前终止,
|
||||
// 发 error 事件而非伪装正常结束(否则客户端拿到"成功的空消息")。
|
||||
func (st *AnthRespBridge) Finish() []AnthEvent {
|
||||
if st.errMsg != "" {
|
||||
return nil
|
||||
}
|
||||
if !st.sawTerminal {
|
||||
st.errMsg = "上游流提前终止,未返回终态事件"
|
||||
return []AnthEvent{st.errorEvent()}
|
||||
}
|
||||
var events []AnthEvent
|
||||
if !st.started {
|
||||
st.started = true
|
||||
|
||||
Reference in New Issue
Block a user