fix(agent): 修复流式推理与工具参数重试
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user