ホームページ  >  記事  >  バックエンド開発  >  Go でクロードから有効な JSON を一貫して取得する方法

Go でクロードから有効な JSON を一貫して取得する方法

Patricia Arquette
Patricia Arquetteオリジナル
2024-10-17 22:22:29281ブラウズ

How to Consistently Retrieve Valid JSON from Claude  in Go

開発者として LLM を使用する場合、構造化された形式でデータを受信したいことがよくあります。 GPT-3 の初期の時代にはこれが常にうまく機能していたわけではありませんでしたが、関数呼び出しをサポートする現在のモデルでははるかに優れています。

この投稿では、CallClaudeForceTool という単純な関数を共有したいと思います。この関数を使用すると、Claude を呼び出して構造化データ/JSON 出力を受け取ることができます。私の場合、これは Golang で書かれており、必要な任意のタイプの構造体を返します。

まず、構造を定義し、それを JSON スキーマの形式で LLM に提供する必要があります。この例では、データをクエリする関数への入力として機能する構造体型を定義しています。

クエリ関数への入力である構造体は次のとおりです。

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

以下は JSON スキーマの定義です。 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 スキーマ構造体を使用しています。そのために依存関係を使用することもできますが、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 を呼び出し、提供された 1 つのツールの使用を強制し、後続のツール/関数出力で Claude に応答したり、最終応答テキスト メッセージを受け取ったりすることなく終了することです。クロードに構造化データを 1 回応答してもらいたいだけです。

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 の「関数呼び出し」の最も明白な使用例です。

    • 関数呼び出し機能の完全なフローには、ツール/関数の出力でクロードに実際に応答し、通常はクロードが最終メッセージで応答するまでやり取りが含まれます。
  • API リクエストの構造化データの生成: LLM を使用して、ユーザー入力に基づいて API リクエストの JSON ペイロードを生成し、データが必要なスキーマに準拠していることを確認できます。

  • データの検証と変換: LLM を使用すると、入力データを検証して構造化形式に変換し、その後の処理やデータベースへの保存に使用できます。

  • 一般に、独自のカスタム分類子をトレーニングせずに、テキストがあり、それを抽出または特定の構造に変換したいときはいつでも使用できます。


それだけです!これが役に立ったかどうかお知らせください。あるいは、アプリケーションで LLM をどのように使用しているかを共有してください。

ビス・デムナクスト!

~ マーティン


メモ:

  • これはもともと私のブログ (https://blog.embiem.me/unleash-your-home-hardware-processing-long-running-tasks-at-home) に投稿されたものです

  • すべてのコードは、MIT ライセンスに基づいて現状のまま提供されます。

  • カバー画像は AI によって生成されました。

以上がGo でクロードから有効な JSON を一貫して取得する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。