You've already forked agentic-coding-workflow
fix(infra/llm): anthropic stream goroutine respects ctx cancel on send
This commit is contained in:
@@ -77,6 +77,15 @@ func (c *AnthropicClient) Stream(ctx context.Context, modelID string, req Reques
|
|||||||
stream := c.sdk.Messages.NewStreaming(ctx, params)
|
stream := c.sdk.Messages.NewStreaming(ctx, params)
|
||||||
out := make(chan StreamEvent, 16)
|
out := make(chan StreamEvent, 16)
|
||||||
|
|
||||||
|
// send 在 ctx 取消时立即放弃发送,防止下游 reader 已退出导致 goroutine 永久阻塞。
|
||||||
|
send := func(ev StreamEvent) bool {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false
|
||||||
|
case out <- ev:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer close(out)
|
defer close(out)
|
||||||
var inputTokens int
|
var inputTokens int
|
||||||
@@ -93,25 +102,33 @@ func (c *AnthropicClient) Stream(ctx context.Context, modelID string, req Reques
|
|||||||
switch ev.Delta.Type {
|
switch ev.Delta.Type {
|
||||||
case "text_delta":
|
case "text_delta":
|
||||||
if text := ev.Delta.AsTextDelta().Text; text != "" {
|
if text := ev.Delta.AsTextDelta().Text; text != "" {
|
||||||
out <- StreamEvent{Type: EventTextDelta, Text: text}
|
if !send(StreamEvent{Type: EventTextDelta, Text: text}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "thinking_delta":
|
case "thinking_delta":
|
||||||
if thinking := ev.Delta.AsThinkingDelta().Thinking; thinking != "" {
|
if thinking := ev.Delta.AsThinkingDelta().Thinking; thinking != "" {
|
||||||
out <- StreamEvent{Type: EventThinkingDelta, Text: thinking}
|
if !send(StreamEvent{Type: EventThinkingDelta, Text: thinking}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "message_delta":
|
case "message_delta":
|
||||||
ev := event.AsMessageDelta()
|
ev := event.AsMessageDelta()
|
||||||
out <- StreamEvent{Type: EventUsage, Usage: &Usage{
|
if !send(StreamEvent{Type: EventUsage, Usage: &Usage{
|
||||||
PromptTokens: inputTokens,
|
PromptTokens: inputTokens,
|
||||||
CompletionTokens: int(ev.Usage.OutputTokens),
|
CompletionTokens: int(ev.Usage.OutputTokens),
|
||||||
}}
|
}}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
case "message_stop":
|
case "message_stop":
|
||||||
out <- StreamEvent{Type: EventDone, Reason: "end_turn"}
|
if !send(StreamEvent{Type: EventDone, Reason: "end_turn"}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := stream.Err(); err != nil {
|
if err := stream.Err(); err != nil {
|
||||||
out <- StreamEvent{Type: EventError, Err: fmt.Errorf("anthropic stream: %w", err)}
|
send(StreamEvent{Type: EventError, Err: fmt.Errorf("anthropic stream: %w", err)})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user