🐥

Anthropic Claudeを用いて、テキストをzodスキーマに沿った形に変換する

2024/11/25に公開

近年、生成AIの人気が高まってきており、特にChatGPTやClaudeなど、テキストを入出力とするAIはすっかり普及してきた。

実は、OpenAIやAnthropicのAPIを使えば、ただのテキストだけでなく、構造化されたデータを出力させることも可能である。
OpenAIのAPIは、レスポンスの形式としてJSONスキーマを指定できる[1]。一方、Claudeはレスポンスの形式を直接指定することはできないが、実は同等のことを実現する方法がある。

最も愚直な方法はプロンプトで「JSON形式で出力してください」と指示することだが、JSON以外の文言が出力されたり、文字列が適切にエスケープされなかったりして安定しない。
tool useを活用するのがより確実な方法である。

tool useとは、AIエージェントの実装に不可欠な 「AIの判断で外部サービスを呼び出させる機能」 である。外部サービスに渡すパラメータの形式をJSON Schemaとして指定する仕組みがClaudeには備わっている。
これを活用し、 「構造化されたデータを出力する外部ツール」 を与えればよい。

https://www.npmjs.com/package/zod-to-json-schema パッケージを使えば、zodからJSON Schemaに変換できる。今回はzodでスキーマを定義してみよう。

import Anthropic from '@anthropic-ai/sdk';
import { Tool } from '@anthropic-ai/sdk/resources/messages.mjs';
import fs from "fs";
import { z } from "zod";
import zodToJsonSchema from 'zod-to-json-schema';

const Bird = z.object({
  commonName: z.string(),
  scientificName: z.string(),
  length: z.number(),
  wingspan: z.number(),
  weight: z.number(),
  order: z.string(),
  family: z.string(),
  genus: z.string(),
  species: z.string(),
  summary: z.string().describe("A brief summary of the species"),
  feeding_habit: z.enum(["herbivore", "carnivore", "omnivore", "insectivore", "frugivore", "nectarivore", "molluscivore", "piscivore", "detritivore"]).array(),
  distribution: z.string().array(),
});

そして、このjsonOutputが今回使用する"外部ツール"の定義だ。

function zodToInputSchema<T>(schema: z.ZodSchema<T>): Tool.InputSchema {
  const jsonSchema = zodToJsonSchema(schema, "input").definitions?.input;
  if (!jsonSchema) {
    throw new Error("Failed to convert Zod schema to JSON schema");
  }
  return jsonSchema as Tool.InputSchema;
}

const jsonOutput = {
  name: "json-output",
  input_schema: zodToInputSchema(Bird),
  description: "Export structured data as JSON",
}

これをClaudeのAPIに渡せばよい。tool_choiceパラメータに{ type: "tool", name: "json-output" }を指定することで、このツールを強制的に呼び出せる。

const client = new Anthropic();

const content = fs.readFileSync(process.stdin.fd, 'utf-8');

const response = await client.messages.create({
  model: "claude-3-5-haiku-latest",
  temperature: 0, // ランダム性・創造性が求められない場合は0にする
  max_tokens: 8000,
  system: "Convert the given text into structured data using json-output tool",
  messages: [{role: "user", content}],
  tools: [jsonOutput],
  tool_choice: {
    type: "tool",
    name: "json-output",
  },
});

for (const block of response.content) {
  if (block.type === "tool_use"){
    console.log(block.input);
  } else {
    console.log(block.text);
  }
}

このプログラムに、Wikipediaのメジロの記事を入力すると、以下のような出力が得られる。

{
  commonName: 'メジロ',
  scientificName: 'Zosterops japonicus',
  length: 12,
  wingspan: 18,
  weight: '<UNKNOWN>',
  class: 'Aves',
  order: 'Passeriformes',
  family: 'Zosteropidae',
  genus: 'Zosterops',
  species: 'japonicus',
  summary: 'A small bird native to Japan, Indonesia, South Korea, Philippines, and East Timor. Known for its distinctive white eye-ring, it is slightly smaller than a sparrow. The bird has a greenish back and dark brown feathers. It is omnivorous, particularly fond of flower nectar and fruits, and often seen feeding on plum and camellia flowers in early spring.',
  feeding_habit: [ 'omnivore', 'nectarivore', 'insectivore' ],
  distribution: [
    'Japan',
    'Indonesia',
    'South Korea',
    'Philippines',
    'East Timor',
    'Hawaii'
  ]
}

想定通りの出力が得られ、今回に関しては正確性も申し分ない。文書を整理する簡単な方法として、いろいろなシーンで活用できそうだ。

Future Work

  • スタンドアロンなコマンドラインツールにする
  • ストリーミングとtool useを両立する(次回執筆予定)
  • 文を正確に引用する仕組み

そもそもなぜAnthropic Claudeを選んだか

筆者が所属している株式会社HERPは、採用に関わる業務を支援するプロダクトを開発しており、個人を特定する情報や年収情報などセンシティブな情報を扱うことも多く、情報の管理には名実ともに配慮が必要である。
OpenAIのGPTはセルフホスティングすることができないため、プロダクトから使おうとするとOpenAIというサードパーティーに情報を渡すことになる。個人情報を扱うHERPのプロダクトにおいて、その選択はリーガル的に取り難い。
一方、Anthropic ClaudeはAWS Bedrockで動かせるため、そういった心配がない。また、特に日本語の入出力において、出力の品質と速度に優れているため[2]Claudeを推奨する方針にした。

そして、株式会社HERPは共にプロダクトを成長させる仲間を募集しています。
https://herp.careers/v1/herpinc/zVEuWwkjSuW5

脚注
  1. Structured Outputs - OpenAI ↩︎

  2. というか、Bedrock上で実用的な日本語処理能力を持つモデルは2024時点でClaudeしかなかった ↩︎

Discussion