首页 >后端开发 >Golang >如何在 Go 中一致地从 Claude 检索有效的 JSON

如何在 Go 中一致地从 Claude 检索有效的 JSON

Patricia Arquette
Patricia Arquette原创
2024-10-17 22:22:29320浏览

How to Consistently Retrieve Valid JSON from Claude  in Go

作为开发人员与法学硕士合作时,您通常希望接收结构化格式的数据。虽然这在早期的 GPT-3 时代并不总是有效,但当前支持函数调用的模型在这方面做得更好。

在这篇文章中,我想分享一个名为 CallClaudeForceTool 的简单函数,您可以使用它来调用 Claude 来接收结构化数据/JSON 输出。就我而言,它是用 Golang 编写的,并返回我需要的任何类型的结构。

首先,您需要定义结构并将其以 JSON 模式的格式提供给 LLM。在我的示例中,我定义了一个结构类型,用作查询某些数据的函数的输入。

这是查询函数的输入的结构:

type QueryArgs struct {
    SearchQuery string `json:"searchQuery,omitempty"`
    TypeFilter  string `json:"typeFilter,omitempty"`
}

以下是 JSON Schema 定义。添加好的名称和描述在这里实际上很重要,因为 LLM 在生成 JSON 数据时将使用此信息:

var QueryFnSchema = JSONSchema{
    Type: ai.DataTypeObject,
    Properties: map[string]ai.JSONSchema{
        "searchQuery": {
            Type:        ai.DataTypeString,
            Description: "The search text query to use. Any data that contains this text will be searched. If not provided, then no text comparison will be performed.",
        },
        "typeFilter": {
            Type:        ai.DataTypeString,
            Description: `The type to filter on. If not provided, data of any type will be searched. Can only be one of: "location", "event", "person", "organization".`,
            Enum:        []string{"location", "event", "person", "organization"},
        },
    },
}

type JSONSchema struct {
    Type        DataType              `json:"type,omitempty"`
    Description string                `json:"description,omitempty"`
    Enum        []string              `json:"enum,omitempty"`
    Properties  map[string]JSONSchema `json:"properties,omitempty"`
    Required    []string              `json:"required,omitempty"`
    Items       *JSONSchema           `json:"items,omitempty"`
}

func (d JSONSchema) MarshalJSON() ([]byte, error) {
    if d.Properties == nil {
        d.Properties = make(map[string]JSONSchema)
    }
    type Alias JSONSchema
    return json.Marshal(struct {
        Alias
    }{
        Alias: (Alias)(d),
    })
}

type DataType string

const (
    DataTypeObject  DataType = "object"
    DataTypeNumber  DataType = "number"
    DataTypeInteger DataType = "integer"
    DataTypeBoolean DataType = "boolean"
    DataTypeString  DataType = "string"
    DataTypeArray   DataType = "array"
    DataTypeNull    DataType = "null"
)

我正在使用一个简单的 JSON Schema 结构。您可以为此使用依赖项,但此结构通常足以满足 LLM 函数调用。它与 Anthropic 的 Claude 和 OpenAI 的 ChatGPT 配合得很好。

您可以根据 QueryArgs 的等效项或您需要并希望 LLM 响应的任何数据来定义自己的架构,而不是 QueryFnSchema。

现在是时候使用 CallClaudeForceTool 函数了(将在帖子末尾解释):

func main() {
// ...
    queryArgs, claudeResponse := CallClaudeForceTool[QueryArgs]([]ClaudeLLMMessage{
            {Role: "user", Content: getQueryPrompt(inputMessage)},
        }, ToolDefinition{
            Name:        "search",
            Description: "Search for data in the database",
            InputSchema: queryFnSchema,
        }, ClaudeConfig{
            MaxTokens:   4000,
            Temperature: 0.8,
            System:      SYSTEM_PROMPT,
        })

    result := db.Query(queryArgs)
// ...
}

您可以根据自己的需要替换 getQueryPrompt(inputMessage) 、 SYSTEM_PROMPT 和任何配置。

queryArgs 变量现在包含您定义的格式的 LLM 生成的数据,在我的例子中是 QueryArgs 结构。此时,我使用它作为实际函数调用的参数来查询我的数据库。但是,您也可以根据某些输入或提示生成任何结构化数据,然后在代码中使用它。

最后,这是 CallClaudeForceTool 函数和任何辅助类型。其目标是调用 Claude,强制其使用提供的单个工具,并结束而不用任何后续工具/函数输出响应 Claude 或接收任何最终响应文本消息。我们只希望 Claude 用结构化数据回复一次。

func CallClaudeForceTool[T any](messages []ClaudeLLMMessage, tool ToolDefinition, config ClaudeConfig) (T, *ClaudeCreateMessageResponse, error) {
    payload := ClaudeCreateMessagePayload{
        Model:    "claude-3-5-sonnet-20240620",
        Messages: messages,
        Tools:    []ToolDefinition{tool},
        ToolChoice: ClaudeToolChoice{
            Type: ToolChoiceTypeTool,
            Name: tool.Name,
        },
        MaxTokens:   config.MaxTokens,
        Temperature: config.Temperature,
        System:      config.System,
    }

    payloadBytes, err := json.Marshal(payload)
    if err != nil {
        return *new(T), nil, fmt.Errorf("impossible to marshal payload: %w", err)
    }

    req, err := http.NewRequest(
        http.MethodPost,
        "https://api.anthropic.com/v1/messages",
        bytes.NewReader(payloadBytes),
    )
    if err != nil {
        return *new(T), nil, fmt.Errorf("impossible to create request: %w", err)
    }
    req.Header.Set("x-api-key", os.Getenv("ANTHROPIC_API_KEY"))
    req.Header.Set("content-type", "application/json")
    req.Header.Set("anthropic-version", "2023-06-01")

    // send the request
    httpClient := http.Client{Timeout: 60 * time.Second}
    res, err := httpClient.Do(req)
    if err != nil {
        return *new(T), nil, fmt.Errorf("impossible to send request: %w", err)
    }
    defer res.Body.Close()

    resBody, err := io.ReadAll(res.Body)
    if err != nil {
        return *new(T), nil, fmt.Errorf("impossible to read all body of response: %w", err)
    }

    // unmarshal the response
    var response ClaudeCreateMessageResponse
    err = json.Unmarshal(resBody, &response)
    if err != nil {
        return *new(T), nil, fmt.Errorf("impossible to unmarshal response: %w", err)
    }

    // Get the tool response
    var toolInput T
    for _, content := range response.Content {
        if content.Type != ClaudeResponseContentTypeToolUse || content.Name != tool.Name {
            continue
        }
        inputBytes, err := json.Marshal(content.Input)
        if err != nil {
            return *new(T), &response, fmt.Errorf("impossible to marshal tool response: %w", err)
        }
        err = json.Unmarshal(inputBytes, &toolInput)
        if err != nil {
            return *new(T), &response, fmt.Errorf("impossible to unmarshal tool response: %w", err)
        }
        return toolInput, &response, nil
    }

    return toolInput, &response, fmt.Errorf("impossible to find tool response")
}

// Auxiliary types

type ClaudeLLMMessage struct {
    Role    string `json:"role"`
    Content string `json:"content"`
}

type ClaudeConfig struct {
    MaxTokens   int
    Temperature float64
    System      string
}

type ClaudeCreateMessagePayload struct {
    Model       string             `json:"model"`
    MaxTokens   int                `json:"max_tokens"`
    Messages    []ClaudeLLMMessage `json:"messages"`
    Stream      bool               `json:"stream,omitempty"`
    System      string             `json:"system,omitempty"`
    Temperature float64            `json:"temperature,omitempty"`
    ToolChoice  ClaudeToolChoice   `json:"tool_choice,omitempty"`
    Tools       []ToolDefinition   `json:"tools,omitempty"`
}

type ClaudeCreateMessageResponse struct {
    Content []struct {
        Type  ClaudeResponseContentType `json:"type"`
        Text  string                    `json:"text,omitempty"`
        ID    string                    `json:"id,omitempty"`
        Name  string                    `json:"name,omitempty"`
        Input map[string]interface{}    `json:"input,omitempty"`
    } `json:"content"`
    Id           string `json:"id"`
    Model        string `json:"model"`
    Role         string `json:"role"`
    StopReason   string `json:"stop_reason"`
    StopSequence string `json:"stop_sequence"`
    Type         string `json:"type"`
    Usage        struct {
        InputTokens  int `json:"input_tokens"`
        OutputTokens int `json:"output_tokens"`
    } `json:"usage"`
}

type ClaudeToolChoice struct {
    Type ToolChoiceType `json:"type"`
    Name string         `json:"name,omitempty"`
}

type ToolChoiceType string

const (
    ToolChoiceTypeAuto ToolChoiceType = "auto"
    ToolChoiceTypeCode ToolChoiceType = "any"
    ToolChoiceTypeTool ToolChoiceType = "tool"
)

type ToolDefinition struct {
    Name        string     `json:"name"`
    Description string     `json:"description"`
    InputSchema JSONSchema `json:"input_schema"`
}

一些可以使用的示例:

  • 调用函数,如我的示例所示,这可能是LLM“函数调用”最明显的用例。

    • 函数调用功能的完整流程将包括使用工具/函数的输出实际响应 Claude,并且通常会有一个来回,直到 Claude 响应其最终消息。
  • 为 API 请求生成结构化数据:您可以使用 LLM 根据用户输入为 API 请求生成 JSON 负载,确保数据符合所需的架构。

  • 数据验证和转换:LLM 可用于验证输入数据并将其转换为结构化格式,然后可用于进一步处理或存储在数据库中。

  • 通常,每当您有一些文本并想要提取或将其转换为特定结构,而不希望训练自己的自定义分类器时。


就是这样!请告诉我这是否有用,或者分享您如何在申请中使用法学硕士。

Bis demnächst!

~马丁


注释:

  • 这最初发布在我的博客上:https://blog.embiem.me/unleash-your-home-hardware-processing-long-running-tasks-at-home

  • 任何代码均在 MIT 许可证下按原样提供。

  • 封面图片是人工智能生成的。

以上是如何在 Go 中一致地从 Claude 检索有效的 JSON的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn