fix(agent): 修复流式推理与工具参数重试

This commit is contained in:
ddCat
2026-09-15 05:22:07 +08:00
parent 06794aea95
commit 201f3aac65
13 changed files with 231 additions and 46 deletions

View File

@@ -79,7 +79,7 @@ func runAgentToolTask(ctx context.Context, input canvasGenerationInput) (map[str
func postAgentRequest(ctx context.Context, input canvasGenerationInput, path string, body map[string]interface{}, protocol string) (map[string]interface{}, error) {
if input.StreamText {
return postStreamingAgent(ctx, input.Config, path, body, protocol, input.OnTextDelta)
return postStreamingAgent(ctx, input.Config, path, body, protocol, input.OnTextDelta, input.OnReasoningDelta)
}
delete(body, "stream")
var payload map[string]interface{}
@@ -90,7 +90,12 @@ func postAgentRequest(ctx context.Context, input canvasGenerationInput, path str
}
func runDeclarativeAgentTask(ctx context.Context, input canvasGenerationInput, adapter protocol.AgentAdapter) (map[string]interface{}, error) {
if input.TextOptions.Thinking {
wire := input.Config.InterfaceType
if wire == string(model.ChannelInterfaceOpenAIResponse) {
wire = "responses"
}
knownWire := wire == "chat-completion" || wire == "responses" || wire == "claude-api"
if input.TextOptions.Thinking && !knownWire {
return nil, errors.New("当前声明式 Agent 渠道尚不支持思考模式,请关闭思考模式或切换内置协议渠道")
}
if input.AgentRequests == nil {
@@ -106,6 +111,37 @@ func runDeclarativeAgentTask(ctx context.Context, input canvasGenerationInput, a
if err != nil {
return nil, err
}
if knownWire {
body := protocolBodyObject(spec.Body)
if body == nil {
return nil, errors.New("声明式 Agent 请求体必须是 JSON 对象")
}
applyTextThinking(body, input, wire)
spec.Body = body
if input.StreamText {
body["stream"] = true
if wire == "chat-completion" {
if err := ensureChatCompletionStreamUsage(body); err != nil {
return nil, err
}
}
parser := newStreamingAgentParser(wire, input.OnTextDelta)
parser.emitReasoning = input.OnReasoningDelta
data, mime, err := executeProtocolBinaryRequestWithConsumer(ctx, input.Config, spec, parser.consume)
if err != nil {
return nil, err
}
if strings.Contains(strings.ToLower(mime), "event-stream") {
parser.flush()
return parser.result()
}
var payload map[string]interface{}
if err := json.Unmarshal(data, &payload); err != nil {
return nil, fmt.Errorf("Agent 接口返回格式无效:%w", err)
}
return parseAgentToolPayload(payload, wire)
}
}
body, err := executeProtocolRequest(ctx, input.Config, spec)
if err != nil {
return nil, err
@@ -330,17 +366,17 @@ func parseAgentToolPayload(payload map[string]interface{}, protocol string) (map
return result, nil
}
func postStreamingAgent(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, onDelta func(string)) (map[string]interface{}, error) {
func postStreamingAgent(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, onDelta func(string), onReasoning ...func(string)) (map[string]interface{}, error) {
body["stream"] = true
if protocol == "chat-completion" {
metadata, _ := ctx.Value(providerAnalyticsKey{}).(providerAnalyticsContext)
if metadata.BillingMode == "token" {
if err := ensureChatCompletionStreamUsage(body); err != nil {
return nil, err
}
if err := ensureChatCompletionStreamUsage(body); err != nil {
return nil, err
}
}
parser := newStreamingAgentParser(protocol, onDelta)
if len(onReasoning) > 0 {
parser.emitReasoning = onReasoning[0]
}
data, mimeType, err := postStreamingBinary(ctx, config, path, body, parser.consume)
if err != nil {
return nil, err
@@ -363,15 +399,16 @@ type streamingAgentToolCall struct {
}
type streamingAgentParser struct {
protocol string
buffer string
text strings.Builder
reasoning strings.Builder
toolCalls map[int]*streamingAgentToolCall
toolCallByID map[string]int
completed map[string]interface{}
err error
emit func(string)
protocol string
buffer string
text strings.Builder
reasoning strings.Builder
toolCalls map[int]*streamingAgentToolCall
toolCallByID map[string]int
completed map[string]interface{}
err error
emit func(string)
emitReasoning func(string)
}
func newStreamingAgentParser(protocol string, emit func(string)) *streamingAgentParser {
@@ -448,7 +485,7 @@ func (p *streamingAgentParser) consumeResponsesEvent(eventName string, payload m
case "response.output_text.delta", "output_text.delta":
p.appendText(stringField(payload, "delta"))
case "response.reasoning.delta", "response.reasoning_text.delta", "response.reasoning_summary_text.delta":
p.reasoning.WriteString(stringField(payload, "delta"))
p.appendReasoning(stringField(payload, "delta"))
case "response.completed":
p.completed, _ = payload["response"].(map[string]interface{})
case "response.output_item.added":
@@ -482,7 +519,7 @@ func (p *streamingAgentParser) consumeChatCompletionEvent(payload map[string]int
choice, _ := value.(map[string]interface{})
delta, _ := choice["delta"].(map[string]interface{})
p.appendText(streamContentText(delta["content"]))
p.reasoning.WriteString(firstNonEmptyString(stringField(delta, "reasoning_content"), stringField(delta, "reasoning"), stringField(delta, "reasoning_text")))
p.appendReasoning(firstNonEmptyString(stringField(delta, "reasoning_content"), stringField(delta, "reasoning"), stringField(delta, "reasoning_text")))
for fallbackIndex, toolValue := range interfaceSlice(delta["tool_calls"]) {
tool, _ := toolValue.(map[string]interface{})
index := intField(tool, "index", fallbackIndex)
@@ -509,7 +546,7 @@ func (p *streamingAgentParser) consumeClaudeEvent(payload map[string]interface{}
case "text":
p.appendText(stringField(block, "text"))
case "thinking":
p.reasoning.WriteString(firstNonEmptyString(stringField(block, "thinking"), stringField(block, "text")))
p.appendReasoning(firstNonEmptyString(stringField(block, "thinking"), stringField(block, "text")))
case "tool_use":
arguments := ""
if input := block["input"]; input != nil {
@@ -526,7 +563,7 @@ func (p *streamingAgentParser) consumeClaudeEvent(payload map[string]interface{}
p.appendText(stringField(delta, "text"))
}
if stringField(delta, "type") == "thinking_delta" {
p.reasoning.WriteString(firstNonEmptyString(stringField(delta, "thinking"), stringField(delta, "text")))
p.appendReasoning(firstNonEmptyString(stringField(delta, "thinking"), stringField(delta, "text")))
}
if stringField(delta, "type") == "input_json_delta" {
p.toolCall(index).arguments += stringField(delta, "partial_json")
@@ -551,6 +588,16 @@ func (p *streamingAgentParser) appendText(delta string) {
}
}
func (p *streamingAgentParser) appendReasoning(delta string) {
if delta == "" {
return
}
p.reasoning.WriteString(delta)
if p.emitReasoning != nil {
p.emitReasoning(delta)
}
}
func (p *streamingAgentParser) toolCall(index int) *streamingAgentToolCall {
if index < 0 {
index = 0