fix(canvas): 在线 Agent - 系统模型接入工具任务路由
This commit is contained in:
1 parent
298b5ebf1f
commit
31026a112d
6 files changed
+239
-25
No files matched your search
@@ -35,11 +35,17 @@ type canvasGenerationInput struct {
|
||||
TextHistory []providerTextMessage `json:"textHistory"`
|
||||
Mask *providerMedia `json:"mask"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
AgentRequests *agentToolRequests `json:"agentRequests"`
|
||||
ImageCapability *ImageCapabilityConfig `json:"-"`
|
||||
StreamText bool `json:"-"` // 分镜请求使用上游 SSE 保活;最终结构仍在流结束后统一校验。
|
||||
VideoCapability *VideoCapabilityConfig `json:"-"`
|
||||
}
|
||||
|
||||
type agentToolRequests struct {
|
||||
Responses map[string]interface{} `json:"responses"`
|
||||
ChatCompletion map[string]interface{} `json:"chatCompletion"`
|
||||
}
|
||||
|
||||
type providerTextMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
@@ -247,6 +253,9 @@ func (s *Service) processCanvasGenerationTask(ctx context.Context, userID string
|
||||
case "image":
|
||||
return runImageTask(ctx, input)
|
||||
case "text":
|
||||
if input.AgentRequests != nil {
|
||||
return runAgentToolTask(ctx, input)
|
||||
}
|
||||
result, taskErr := runTextTask(ctx, input)
|
||||
if taskErr == nil && promptTemplateOperation != "" {
|
||||
taskErr = validatePromptTemplateResult(promptTemplateOperation, result)
|
||||
@@ -261,6 +270,78 @@ func (s *Service) processCanvasGenerationTask(ctx context.Context, userID string
|
||||
}
|
||||
}
|
||||
|
||||
func runAgentToolTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
request := input.AgentRequests.ChatCompletion
|
||||
path := "/chat/completions"
|
||||
protocol := "chat-completion"
|
||||
if input.Config.InterfaceType == string(model.ChannelInterfaceOpenAIResponse) {
|
||||
request = input.AgentRequests.Responses
|
||||
path = "/responses"
|
||||
protocol = "responses"
|
||||
}
|
||||
if request == nil {
|
||||
return nil, errors.New("画布 Agent 工具请求缺少协议参数")
|
||||
}
|
||||
body := cloneStringAnyMap(request)
|
||||
body["model"] = input.Config.Model
|
||||
var payload map[string]interface{}
|
||||
if err := postJSON(ctx, input.Config, path, body, &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseAgentToolPayload(payload, protocol)
|
||||
}
|
||||
|
||||
func parseAgentToolPayload(payload map[string]interface{}, protocol string) (map[string]interface{}, error) {
|
||||
if err := validateTextPayload(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := map[string]interface{}{"mode": "text", "text": "", "toolCalls": []interface{}{}}
|
||||
if protocol == "responses" {
|
||||
result["text"] = firstNonEmptyString(stringField(payload, "output_text"), extractResponseText(payload))
|
||||
calls := make([]interface{}, 0)
|
||||
for _, value := range interfaceSlice(payload["output"]) {
|
||||
item, _ := value.(map[string]interface{})
|
||||
if stringField(item, "type") != "function_call" {
|
||||
continue
|
||||
}
|
||||
calls = append(calls, map[string]interface{}{"id": firstNonEmptyString(stringField(item, "call_id"), stringField(item, "id")), "type": "function", "function": map[string]interface{}{"name": stringField(item, "name"), "arguments": stringField(item, "arguments")}})
|
||||
}
|
||||
result["toolCalls"] = calls
|
||||
return result, nil
|
||||
}
|
||||
choices := interfaceSlice(payload["choices"])
|
||||
if len(choices) == 0 {
|
||||
return nil, errors.New("画布 Agent 接口没有返回 choices")
|
||||
}
|
||||
choice, _ := choices[0].(map[string]interface{})
|
||||
message, _ := choice["message"].(map[string]interface{})
|
||||
result["text"] = stringField(message, "content")
|
||||
if reasoning := firstNonEmptyString(stringField(message, "reasoning_content"), stringField(message, "reasoning")); reasoning != "" {
|
||||
result["reasoning"] = reasoning
|
||||
}
|
||||
calls := make([]interface{}, 0)
|
||||
for _, value := range interfaceSlice(message["tool_calls"]) {
|
||||
item, _ := value.(map[string]interface{})
|
||||
function, _ := item["function"].(map[string]interface{})
|
||||
calls = append(calls, map[string]interface{}{"id": stringField(item, "id"), "type": "function", "function": map[string]interface{}{"name": stringField(function, "name"), "arguments": stringField(function, "arguments")}})
|
||||
}
|
||||
result["toolCalls"] = calls
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func interfaceSlice(value interface{}) []interface{} {
|
||||
items, _ := value.([]interface{})
|
||||
return items
|
||||
}
|
||||
|
||||
func cloneStringAnyMap(value map[string]interface{}) map[string]interface{} {
|
||||
cloned := make(map[string]interface{}, len(value)+1)
|
||||
for key, item := range value {
|
||||
cloned[key] = item
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
type styleExecutionPlanDocument struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
ProfilePresetID string `json:"profilePresetId"`
|
||||
|
||||
@@ -107,6 +107,59 @@ data: [DONE]
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAgentToolPayloadSupportsChatCompletions(t *testing.T) {
|
||||
result, err := parseAgentToolPayload(map[string]interface{}{
|
||||
"choices": []interface{}{map[string]interface{}{
|
||||
"message": map[string]interface{}{
|
||||
"content": "准备读取画布",
|
||||
"tool_calls": []interface{}{map[string]interface{}{
|
||||
"id": "call-1",
|
||||
"function": map[string]interface{}{"name": "canvas_get_state", "arguments": `{}`},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}, "chat-completion")
|
||||
if err != nil {
|
||||
t.Fatalf("parseAgentToolPayload() error = %v", err)
|
||||
}
|
||||
if result["text"] != "准备读取画布" {
|
||||
t.Fatalf("text = %v", result["text"])
|
||||
}
|
||||
calls, _ := result["toolCalls"].([]interface{})
|
||||
if len(calls) != 1 {
|
||||
t.Fatalf("toolCalls = %#v", result["toolCalls"])
|
||||
}
|
||||
call, _ := calls[0].(map[string]interface{})
|
||||
function, _ := call["function"].(map[string]interface{})
|
||||
if call["id"] != "call-1" || function["name"] != "canvas_get_state" || function["arguments"] != `{}` {
|
||||
t.Fatalf("tool call = %#v", call)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAgentToolPayloadSupportsResponses(t *testing.T) {
|
||||
result, err := parseAgentToolPayload(map[string]interface{}{
|
||||
"output": []interface{}{
|
||||
map[string]interface{}{"type": "message", "content": []interface{}{map[string]interface{}{"type": "output_text", "text": "开始操作"}}},
|
||||
map[string]interface{}{"type": "function_call", "call_id": "call-2", "name": "canvas_apply_ops", "arguments": `{"ops":[]}`},
|
||||
},
|
||||
}, "responses")
|
||||
if err != nil {
|
||||
t.Fatalf("parseAgentToolPayload() error = %v", err)
|
||||
}
|
||||
if result["text"] != "开始操作" {
|
||||
t.Fatalf("text = %v", result["text"])
|
||||
}
|
||||
calls, _ := result["toolCalls"].([]interface{})
|
||||
if len(calls) != 1 {
|
||||
t.Fatalf("toolCalls = %#v", result["toolCalls"])
|
||||
}
|
||||
call, _ := calls[0].(map[string]interface{})
|
||||
function, _ := call["function"].(map[string]interface{})
|
||||
if call["id"] != "call-2" || function["name"] != "canvas_apply_ops" || function["arguments"] != `{"ops":[]}` {
|
||||
t.Fatalf("tool call = %#v", call)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostStreamingTextSetsStreamHeaders(t *testing.T) {
|
||||
t.Setenv("CANVAS_ALLOW_PRIVATE_UPSTREAMS", "true")
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in new issue
Block a user