🦁

LangChain で構造化データを取得(Node.js)

2024/05/26に公開

はじめに

この記事では、LLM では、モデルから構造化データを取得できます。この記事では、LangChain でモデルから構造化データを返す方法について説明します。具体的には以下の記事を参考に記述します。

https://js.langchain.com/v0.2/docs/how_to/structured_output/

TypeScript / JavaScript での GitHub リポジトリーを公開している実装例はすくないので記事化しました。作業リポジトリはこちらです。

https://github.com/hayato94087/langchain-extract-structured-data-demo

LangChain x TypeScript での実装例を以下の記事で紹介しています。

LangChain とは

LangChain は、大規模言語モデル(LLM)を活用したアプリケーションの開発を支援するフレームワークです。

https://js.langchain.com/v0.2/docs/introduction/

構造化データを取得する方法

LangChain でモデルから構造化データを返す方法について説明します。Zod で型を定義し、.withStructuredOutput を使うことで構造化データを取得できます。

import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import 'dotenv/config'

// model
const model = new ChatOpenAI({
  model: "gpt-3.5-turbo",
  temperature: 0
});

// schema
const joke = z.object({
  setup: z.string().describe("ジョークの前置き"),
  punchline: z.string().describe("ジョークのオチ"),
  rating: z.number().optional().describe("ジョークの面白さを1から10で評価"),
});

// structured output
const llm = model.withStructuredOutput(joke);

// invoke
const result = await llm.invoke("猫についてジョークを言ってください");
console.log(result)

作業プロジェクトの準備

TypeScript の簡易プロジェクトを作成します。

長いので折りたたんでおきます。

package.json を作成

package.json を作成します。

$ mkdir -p langchain-extract-structured-data-demo
$ cd langchain-extract-structured-data-demo
$ pnpm init

下記で package.json を上書きします。

package.json
{
  "name": "langchain-extract-structured-data-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "typecheck": "tsc --noEmit",
    "dev": "vite-node index.ts",
    "build": "tsc"
  },
  "keywords": [],
  "author": "",
}

TypeScript & vite-node をインストール

TypeScript と vite-node をインストールします。補足としてこちらの理由のため ts-node ではなく vite-node を利用します。

$ pnpm install -D typescript vite-node @types/node

TypeScriptの設定ファイルを作成

tsconfig.json を作成します。

$ npx tsc --init

tsconfig.json を上書きします。

tsconfig.json
{
  "compilerOptions": {
    /* Base Options: */
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "ES2022",
    "allowJs": true,
    "resolveJsonModule": true,
    "moduleDetection": "force",
    "isolatedModules": true,
    
    /* Strictness */
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "checkJs": true,

    /* Bundled projects */
    "noEmit": true,
    "outDir": "dist",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "preserve",
    "incremental": true,
    "sourceMap": true,
  },
  "include": ["**/*.ts", "**/*.js"],
  "exclude": ["node_modules", "dist"]
}

git を初期化します。

$ git init

.gitignore を作成します。

$ touch .gitignore
.gitignore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build
dist/

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

動作確認コードを作成

動作を確認するためのコードを作成します。

$ touch index.ts
index.ts
console.log('Hello, World');

型チェック

型チェックします。

$ pnpm run typecheck

動作確認

動作確認を実施します。

$ pnpm run dev

Hello, World

コミットします。

$ git add .
$ git commit -m "初回コミット"

LangChain をインストール

LangChain をインストールします。

$ pnpm add langchain @langchain/core

コミットします。

$ git add .
$ git commit -m "LangChainをインストール"

言語モデルの選択

LangChain は、多くの異なる言語モデルをサポートしており、それらを自由に選んで使用できます。

例えば、以下のような言語モデルを選択できます。

  • OpenAI
  • Anthropic
  • FireworksAI
  • MistralAI
  • Groq
  • VertexAI

ここでは OpenAI を利用します。OpenAI を LangChain で利用するためのパッケージをインストールします。

$ pnpm add @langchain/openai

コミットします。

$ git add .
$ git commit -m "LangChainでOpenAIを利用するためのパッケージをインストール"

OpenAI API キーを取得

OpenAI API キーの取得方法はこちらを参照してください。

https://zenn.dev/hayato94087/articles/85378e1f7bc0e5#openai-の-apiキーの取得

環境変数の設定

環境変数に OpenAI キーを追加します。<your-api-key> に自身の API キーを設定してください。

$ touch .env
.env
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'

Node.js で環境変数を利用するために dotenv をインストールします。

$ pnpm i -D dotenv

コミットします。

$ touch .env.example
.env
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'
$ git add .
$ git commit -m "環境変数を設定"

基礎編

まず、シンプルに LLM を使ってみます。

コードを作成

コードを作成します。

$ touch demo01.ts
demo01.ts
import { ChatOpenAI } from "@langchain/openai";
import 'dotenv/config'

const model = new ChatOpenAI({
  model: "gpt-3.5-turbo",
  temperature: 0
});

const result = await model.invoke("猫についてジョークを言ってください");
console.log(result)

ローカルで実行します。

$ pnpm vite-node demo01.ts

AIMessage {
  lc_serializable: true,
  lc_kwargs: {
    content: 'Q: 猫がパーティーで何を飲む?\nA: ミャウハイボール!',
    tool_calls: [],
    invalid_tool_calls: [],
    additional_kwargs: { function_call: undefined, tool_calls: undefined },
    response_metadata: {}
  },
  lc_namespace: [ 'langchain_core', 'messages' ],
  content: 'Q: 猫がパーティーで何を飲む?\nA: ミャウハイボール!',
  name: undefined,
  additional_kwargs: { function_call: undefined, tool_calls: undefined },
  response_metadata: {
    tokenUsage: { completionTokens: 32, promptTokens: 22, totalTokens: 54 },
    finish_reason: 'stop'
  },
  tool_calls: [],
  invalid_tool_calls: []
}

コミットします。

$ git add .
$ git commit -m "LLMを使ってみる"

コードの解説

OpenAI を利用します。

import { ChatOpenAI } from "@langchain/openai";

gpt-3.5-turbo のモデルを選択します。temperature は 0 に設定します。temperature が低いほど、モデルの出力はより予測可能になります。

const model = new ChatOpenAI({
  model: "gpt-3.5-turbo",
  temperature: 0
});

.invoke() を利用して、モデルにテキストを送信します。

const result = await model.invoke("猫についてジョークを言ってください");

Zod を使う方法

OpenAI を利用の場合、.withStructuredOutput() メソッドを利用することで構造化データを取得できます。どのような構造化データが欲しいかは Zod のスキーマを定義します。

インストール

Zod をインストールします。

$ pnpm add zod

コードを作成

コードを作成します。

$ touch demo02.ts
demo02.ts
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import 'dotenv/config'

// model
const model = new ChatOpenAI({
  model: "gpt-3.5-turbo",
  temperature: 0
});


// schema
const joke = z.object({
  setup: z.string().describe("ジョークの前置き"),
  punchline: z.string().describe("ジョークのオチ"),
  rating: z.number().optional().describe("ジョークの面白さを1から10で評価"),
});

// structured output
const llm = model.withStructuredOutput(joke);

// invoke
const result = await llm.invoke("猫についてジョークを言ってください");
console.log(result)

ローカルで実行します。

$ pnpm vite-node demo02.ts

{ setup: 'なぜ猫はコンピューターが好きなのか?', punchline: 'マウスがいるから!', rating: 8 }

コミットします。

$ git add .
$ git commit -m "Zodを利用し構造化データを取得する"

コードを解説

構造化データのスキーマを Zod で定義します。

// schema
const joke = z.object({
  setup: z.string().describe("ジョークの前置き"),
  punchline: z.string().describe("ジョークのオチ"),
  rating: z.number().optional().describe("ジョークの面白さを1から10で評価"),
});

.withStructuredOutput() メソッドを利用することで、構造化データを取得できるようになります。

// structured output
const llm = model.withStructuredOutput(joke);

Zod を使わない方法

Zod を使わずに、.withStructuredOutput() メソッドを利用して構造化データを取得する方法もあります。

コードを作成

コードを作成します。

$ touch demo03.ts
demo03.ts
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import 'dotenv/config'

// model
const model = new ChatOpenAI({
  model: "gpt-3.5-turbo",
  temperature: 0
});


// schema
const parameters = {
  title: "Joke",
  type: "object",
  properties: {
    setup: { type: "string", description: "ジョークの前置き" },
    punchline: { type: "string", description: "ジョークのオチ" },
  },
  required: ["setup", "punchline"],
}

// structured output
const llm = model.withStructuredOutput({
  name: "joke",
  description: "ユーザーに伝えるジョーク",
  parameters
});

// invoke
const result = await llm.invoke("猫についてジョークを言ってください");
console.log(result)

ローカルで実行します。

$ pnpm vite-node demo03.ts

{ setup: 'なぜ猫はコンピューターが好きなのか?', punchline: 'マウスがいるから!' }

コミットします。

$ git add .
$ git commit -m "Zodを利用せずに構造化データを取得する"

コードを解説

構造化データのスキーマを Zod で定義します。

// schema
const parameters = {
  title: "Joke",
  type: "object",
  properties: {
    setup: { type: "string", description: "ジョークの前置き" },
    punchline: { type: "string", description: "ジョークのオチ" },
  },
  required: ["setup", "punchline"],
}

.withStructuredOutput() メソッドを利用することで、構造化データを取得できるようになります。

// structured output
const llm = model.withStructuredOutput({
  name: "joke",
  description: "ユーザーに伝えるジョーク",
  parameters
});

Zod を利用したほうが記述は直感的です。

さいごに

LangChain でモデルから構造化データを返す方法について説明しました。LangChain を使って、モデルから構造化データを取得する方法を理解しました。Zod を利用することで、構造化データのスキーマを定義できます。Zod を使わずに構造化データを取得する方法もありますが、Zod を使ったほうが記述は直感的です。

作業リポジトリ

作業リポジトリはこちらです。

https://github.com/hayato94087/langchain-extract-structured-data-demo

Discussion