mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-10 01:54:46 +08:00
214 lines
5.1 KiB
Go
214 lines
5.1 KiB
Go
|
|
package service
|
|||
|
|
|
|||
|
|
import "strings"
|
|||
|
|
|
|||
|
|
// NeedsToolContinuation 判定请求是否需要工具调用续链处理。
|
|||
|
|
// 满足以下任一信号即视为续链:previous_response_id、input 内包含 function_call_output/item_reference、
|
|||
|
|
// 或显式声明 tools/tool_choice。
|
|||
|
|
func NeedsToolContinuation(reqBody map[string]any) bool {
|
|||
|
|
if reqBody == nil {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if hasNonEmptyString(reqBody["previous_response_id"]) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
if hasToolsSignal(reqBody) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
if hasToolChoiceSignal(reqBody) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
if inputHasType(reqBody, "function_call_output") {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
if inputHasType(reqBody, "item_reference") {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HasFunctionCallOutput 判断 input 是否包含 function_call_output,用于触发续链校验。
|
|||
|
|
func HasFunctionCallOutput(reqBody map[string]any) bool {
|
|||
|
|
if reqBody == nil {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
return inputHasType(reqBody, "function_call_output")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HasToolCallContext 判断 input 是否包含带 call_id 的 tool_call/function_call,
|
|||
|
|
// 用于判断 function_call_output 是否具备可关联的上下文。
|
|||
|
|
func HasToolCallContext(reqBody map[string]any) bool {
|
|||
|
|
if reqBody == nil {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
input, ok := reqBody["input"].([]any)
|
|||
|
|
if !ok {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
for _, item := range input {
|
|||
|
|
itemMap, ok := item.(map[string]any)
|
|||
|
|
if !ok {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
itemType, _ := itemMap["type"].(string)
|
|||
|
|
if itemType != "tool_call" && itemType != "function_call" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if callID, ok := itemMap["call_id"].(string); ok && strings.TrimSpace(callID) != "" {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// FunctionCallOutputCallIDs 提取 input 中 function_call_output 的 call_id 集合。
|
|||
|
|
// 仅返回非空 call_id,用于与 item_reference.id 做匹配校验。
|
|||
|
|
func FunctionCallOutputCallIDs(reqBody map[string]any) []string {
|
|||
|
|
if reqBody == nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
input, ok := reqBody["input"].([]any)
|
|||
|
|
if !ok {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
ids := make(map[string]struct{})
|
|||
|
|
for _, item := range input {
|
|||
|
|
itemMap, ok := item.(map[string]any)
|
|||
|
|
if !ok {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
itemType, _ := itemMap["type"].(string)
|
|||
|
|
if itemType != "function_call_output" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if callID, ok := itemMap["call_id"].(string); ok && strings.TrimSpace(callID) != "" {
|
|||
|
|
ids[callID] = struct{}{}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if len(ids) == 0 {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
result := make([]string, 0, len(ids))
|
|||
|
|
for id := range ids {
|
|||
|
|
result = append(result, id)
|
|||
|
|
}
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HasFunctionCallOutputMissingCallID 判断是否存在缺少 call_id 的 function_call_output。
|
|||
|
|
func HasFunctionCallOutputMissingCallID(reqBody map[string]any) bool {
|
|||
|
|
if reqBody == nil {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
input, ok := reqBody["input"].([]any)
|
|||
|
|
if !ok {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
for _, item := range input {
|
|||
|
|
itemMap, ok := item.(map[string]any)
|
|||
|
|
if !ok {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
itemType, _ := itemMap["type"].(string)
|
|||
|
|
if itemType != "function_call_output" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
callID, _ := itemMap["call_id"].(string)
|
|||
|
|
if strings.TrimSpace(callID) == "" {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HasItemReferenceForCallIDs 判断 item_reference.id 是否覆盖所有 call_id。
|
|||
|
|
// 用于仅依赖引用项完成续链场景的校验。
|
|||
|
|
func HasItemReferenceForCallIDs(reqBody map[string]any, callIDs []string) bool {
|
|||
|
|
if reqBody == nil || len(callIDs) == 0 {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
input, ok := reqBody["input"].([]any)
|
|||
|
|
if !ok {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
referenceIDs := make(map[string]struct{})
|
|||
|
|
for _, item := range input {
|
|||
|
|
itemMap, ok := item.(map[string]any)
|
|||
|
|
if !ok {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
itemType, _ := itemMap["type"].(string)
|
|||
|
|
if itemType != "item_reference" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
idValue, _ := itemMap["id"].(string)
|
|||
|
|
idValue = strings.TrimSpace(idValue)
|
|||
|
|
if idValue == "" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
referenceIDs[idValue] = struct{}{}
|
|||
|
|
}
|
|||
|
|
if len(referenceIDs) == 0 {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
for _, callID := range callIDs {
|
|||
|
|
if _, ok := referenceIDs[callID]; !ok {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// inputHasType 判断 input 中是否存在指定类型的 item。
|
|||
|
|
func inputHasType(reqBody map[string]any, want string) bool {
|
|||
|
|
input, ok := reqBody["input"].([]any)
|
|||
|
|
if !ok {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
for _, item := range input {
|
|||
|
|
itemMap, ok := item.(map[string]any)
|
|||
|
|
if !ok {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
itemType, _ := itemMap["type"].(string)
|
|||
|
|
if itemType == want {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// hasNonEmptyString 判断字段是否为非空字符串。
|
|||
|
|
func hasNonEmptyString(value any) bool {
|
|||
|
|
stringValue, ok := value.(string)
|
|||
|
|
return ok && strings.TrimSpace(stringValue) != ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// hasToolsSignal 判断 tools 字段是否显式声明(存在且不为空)。
|
|||
|
|
func hasToolsSignal(reqBody map[string]any) bool {
|
|||
|
|
raw, exists := reqBody["tools"]
|
|||
|
|
if !exists || raw == nil {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if tools, ok := raw.([]any); ok {
|
|||
|
|
return len(tools) > 0
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// hasToolChoiceSignal 判断 tool_choice 是否显式声明(非空或非 nil)。
|
|||
|
|
func hasToolChoiceSignal(reqBody map[string]any) bool {
|
|||
|
|
raw, exists := reqBody["tool_choice"]
|
|||
|
|
if !exists || raw == nil {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
switch value := raw.(type) {
|
|||
|
|
case string:
|
|||
|
|
return strings.TrimSpace(value) != ""
|
|||
|
|
case map[string]any:
|
|||
|
|
return len(value) > 0
|
|||
|
|
default:
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
}
|