155 lines
4.1 KiB
Go
155 lines
4.1 KiB
Go
package anthropic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/ajac-zero/latticelm/internal/api"
|
|
"github.com/anthropics/anthropic-sdk-go"
|
|
)
|
|
|
|
// parseTools converts Open Responses tools to Anthropic format
|
|
func parseTools(req *api.ResponseRequest) ([]anthropic.ToolUnionParam, error) {
|
|
if req.Tools == nil || len(req.Tools) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var toolDefs []map[string]interface{}
|
|
if err := json.Unmarshal(req.Tools, &toolDefs); err != nil {
|
|
return nil, fmt.Errorf("unmarshal tools: %w", err)
|
|
}
|
|
|
|
var tools []anthropic.ToolUnionParam
|
|
for _, td := range toolDefs {
|
|
// Extract: name, description, parameters
|
|
// Note: Anthropic uses "input_schema" instead of "parameters"
|
|
name, _ := td["name"].(string)
|
|
desc, _ := td["description"].(string)
|
|
params, _ := td["parameters"].(map[string]interface{})
|
|
|
|
inputSchema := anthropic.ToolInputSchemaParam{
|
|
Type: "object",
|
|
Properties: params["properties"],
|
|
}
|
|
|
|
// Add required fields if present
|
|
if required, ok := params["required"].([]interface{}); ok {
|
|
requiredStrs := make([]string, 0, len(required))
|
|
for _, r := range required {
|
|
if str, ok := r.(string); ok {
|
|
requiredStrs = append(requiredStrs, str)
|
|
}
|
|
}
|
|
inputSchema.Required = requiredStrs
|
|
}
|
|
|
|
// Create the tool using ToolUnionParamOfTool
|
|
tool := anthropic.ToolUnionParamOfTool(inputSchema, name)
|
|
|
|
if desc != "" {
|
|
tool.OfTool.Description = anthropic.String(desc)
|
|
}
|
|
|
|
tools = append(tools, tool)
|
|
}
|
|
|
|
return tools, nil
|
|
}
|
|
|
|
// parseToolChoice converts Open Responses tool_choice to Anthropic format
|
|
func parseToolChoice(req *api.ResponseRequest) (anthropic.ToolChoiceUnionParam, error) {
|
|
var result anthropic.ToolChoiceUnionParam
|
|
|
|
if req.ToolChoice == nil || len(req.ToolChoice) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
var choice interface{}
|
|
if err := json.Unmarshal(req.ToolChoice, &choice); err != nil {
|
|
return result, fmt.Errorf("unmarshal tool_choice: %w", err)
|
|
}
|
|
|
|
// Handle string values: "auto", "any", "required"
|
|
if str, ok := choice.(string); ok {
|
|
switch str {
|
|
case "auto":
|
|
result.OfAuto = &anthropic.ToolChoiceAutoParam{
|
|
Type: "auto",
|
|
}
|
|
case "any", "required":
|
|
result.OfAny = &anthropic.ToolChoiceAnyParam{
|
|
Type: "any",
|
|
}
|
|
case "none":
|
|
result.OfNone = &anthropic.ToolChoiceNoneParam{
|
|
Type: "none",
|
|
}
|
|
default:
|
|
return result, fmt.Errorf("unknown tool_choice string: %s", str)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Handle specific tool selection: {"type": "tool", "function": {"name": "..."}}
|
|
if obj, ok := choice.(map[string]interface{}); ok {
|
|
// Check for OpenAI format: {"type": "function", "function": {"name": "..."}}
|
|
if funcObj, ok := obj["function"].(map[string]interface{}); ok {
|
|
if name, ok := funcObj["name"].(string); ok {
|
|
result.OfTool = &anthropic.ToolChoiceToolParam{
|
|
Type: "tool",
|
|
Name: name,
|
|
}
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
// Check for direct name field
|
|
if name, ok := obj["name"].(string); ok {
|
|
result.OfTool = &anthropic.ToolChoiceToolParam{
|
|
Type: "tool",
|
|
Name: name,
|
|
}
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
return result, fmt.Errorf("invalid tool_choice format")
|
|
}
|
|
|
|
// extractToolCalls converts Anthropic content blocks to api.ToolCall
|
|
func extractToolCalls(content []anthropic.ContentBlockUnion) []api.ToolCall {
|
|
var toolCalls []api.ToolCall
|
|
|
|
for _, block := range content {
|
|
// Check if this is a tool_use block
|
|
if block.Type == "tool_use" {
|
|
// Cast to ToolUseBlock to access the fields
|
|
toolUse := block.AsToolUse()
|
|
|
|
// Marshal the input to JSON string for Arguments
|
|
argsJSON, _ := json.Marshal(toolUse.Input)
|
|
|
|
toolCalls = append(toolCalls, api.ToolCall{
|
|
ID: toolUse.ID,
|
|
Name: toolUse.Name,
|
|
Arguments: string(argsJSON),
|
|
})
|
|
}
|
|
}
|
|
|
|
return toolCalls
|
|
}
|
|
|
|
// extractToolCallDelta extracts tool call delta from streaming content block delta
|
|
func extractToolCallDelta(delta anthropic.RawContentBlockDeltaUnion, index int) *api.ToolCallDelta {
|
|
// Check if this is an input_json_delta (streaming tool arguments)
|
|
if delta.Type == "input_json_delta" {
|
|
return &api.ToolCallDelta{
|
|
Index: index,
|
|
Arguments: delta.PartialJSON,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|