Closed9

LLM プロバイダーの API を色々な方法で使ってみる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

curl で使う

https://openrouter.ai/docs/quickstart

コマンド
OPENROUTER_API_KEY="xxxx"
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -d '{
  "model": "deepseek/deepseek-chat-v3-0324:free",
  "messages": [
    {
      "role": "user",
      "content": "人生の意味は何でしょうか?"
    }
  ]
}'
コンソール出力
{
	"id": "gen-1746607102-jpnpg69pvk4fBQSr3P9U",
	"provider": "Chutes",
	"model": "deepseek/deepseek-chat-v3-0324:free",
	"object": "chat.completion",
	"created": 1746607102,
	"choices": [
		{
			"logprobs": null,
			"finish_reason": "stop",
			"native_finish_reason": "stop",
			"index": 0,
			"message": {
				"role": "assistant",
				"content": "「人生の意味」は、人類が古くから問い続けてきた深遠なテーマです。答えは人それぞれ異なりますが、いくつかの視点から考えてみましょう。\n\n### 1. **哲学的な視点**\n   - **実存主義(サルトル、カミュなど)**:  \n     「人生にはあらかじめ決められた意味はない。自分で意味を見いだすことが人間の自由であり責任である」と説きます。例えば、カミュの『シーシュポスの神話』では、無意味な行為の繰り返しの中にこそ幸福を見出す可能性が示されています。\n   - **東洋思想(仏教、禅など)**:  \n     「執着を手放し、『今ここ』に集中することで悟りに至る」という考え方もあります。意味を「探す」のではなく、「気づく」プロセスを重視します。\n\n### 2. **科学的な視点**\n   - **進化生物学**:  \n     生物学的には、人生の目的は「遺伝子の継承」や「種の存続」とも言えますが、これは人間の文化的・精神的な営みを完全には説明できません。\n   - **神経科学**:  \n     脳は「意味を見いだすようにプログラムされている」という研究もあります。例えば、愛や創造性、他者とのつながりによって幸福感が生まれるのは、進化的に意義のある行動だからかもしれません。\n\n### 3. **個人の実践的な答え**\n   - **「意味は作るもの」という立場**:  \n     ヴィクトール・フランクルの『夜と霧』では、極限状態でも「自分に与えられた使命」を見出した人が生き延びたと記されています。例えば、  \n     - **愛する人との関係**  \n     - **社会への貢献**  \n     - **創造的な活動(芸術、発明など)**  \n     こうした具体的な行為を通じて、個人は主観的な「意味」を構築できます。\n\n### 4. **現代社会におけるヒント**\n   - **「小さな意味」の積み重ね**:  \n     壮大な答えを探すのではなく、日々の「なぜ生きるのか?」に答える小さな瞬間(子どもの笑顔、仕事の達成感、自然の美しさなど)に注目する方法もあります。\n   - **レジリエンス(回復力)**:  \n     苦悩が意味を曇らせるときも、「この経験から何を学べるか?」と問い直すことで、新たな解釈が生まれることがあります。\n\n### 最後に\n「人生に意味はあるか?」と問うこと自体が、すでに意味の始まりかもしれません。例えば、  \n- **疑問を持つこと** = 知性の証  \n- **探求する過程** = 成長の機会  \n- **答えの不在** = 自由の余地  \n\nあなた自身の物語を紡ぎながら、その都度、自分なりの答えを更新していく——それも一つの生き方ではないでしょうか。  \n\n何か特定の悩みや背景があれば、さらに深く話し合うこともできますよ。",
				"refusal": null,
				"reasoning": null
			}
		}
	],
	"usage": {
		"prompt_tokens": 10,
		"completion_tokens": 719,
		"total_tokens": 729,
		"prompt_tokens_details": null
	}
}

結果が表示されるまでには 1〜2 分時間がかかった。

https://openrouter.ai/docs/api-reference/streaming

こちらに書いてある通り、ストリーミングが必要かも知れない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

OpenAI SDK を使う

コマンド
cd ~/workspace
mkdir llm-api
cd llm-api
npm init -y
pnpm i openai
pnpm i -D @types/node typescript tsx
pnpm add --save-dev --save-exact @biomejs/biome
touch main.ts .env
main.ts
import OpenAI from "openai";

const openai = new OpenAI({
	baseURL: "https://openrouter.ai/api/v1",
	apiKey: process.env.OPENROUTER_API_KEY,
});

async function main() {
	const completion = await openai.chat.completions.create({
		model: "deepseek/deepseek-chat-v3-0324:free",
		messages: [
			{
				role: "user",
				content: "人生の意味は何ですか?",
			},
		],
	});

	console.log(JSON.stringify(completion, null, 2));
}

main().catch((error) => {
	console.error(error);
});
.env
OPENROUTER_API_KEY="xxxx"
コマンド
pnpm exec tsx --env-file=.env main.ts
コンソール出力
{
  "id": "gen-1746608041-os3GF6FEFU7i2JX2wfNB",
  "provider": "Chutes",
  "model": "deepseek/deepseek-chat-v3-0324:free",
  "object": "chat.completion",
  "created": 1746608041,
  "choices": [
    {
      "logprobs": null,
      "finish_reason": "stop",
      "native_finish_reason": "stop",
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "人生の意味についての問いは、古今東西の哲学者、思想家、宗教家が探求してきた普遍的なテーマです。答えは人それぞれ異なりますが、いくつかの視点をご紹介します。\n\n1. **存在主義的視点**  \n「人生にはあらかじめ決まった意味はない。自ら意味を見出すことで生きる価値が生まれる」という考え方(サルトル・カミュなど)。つまり、**「意味は与えられるものではなく、創造するもの」**という立場です。\n\n2. **宗教的視点**  \n多くの宗教では「神や宇宙との調和」「魂の成長」「因果応報(カルマ)の完成」などを人生の目的とします。例えば仏教では「苦の克服と悟り」、キリスト教では「神への愛と隣人愛の実践」が重視されます。\n\n3. **生物学的視点**  \n進化論的には「遺伝子の継承」や「種の保存」が目的と言えますが、人間の文化的営みはこれを超えた次元で意味を形成しています。\n\n4. **つながりと貢献**  \n多くの思想家(ヴィクトール・フランクルなど)が指摘するように、**他者や社会との関わり**(愛・友情・共同体への参加)、**創造的な活動**(仕事・芸術)、**苦難への態度**を通じて意味が生まれると説きます。\n\n### 現代におけるヒント\n- **小さな意味の積み重ね**:壮大な「人生の意味」を探すよりも、日々の小さな喜び(朝日の美しさ、誰かの笑顔など)に価値を見出す。\n- **成長と変化**:経験を通じて自分が変わるプロセス自体に意味があるとする考え方。\n- **レガシー(遺産)**:未来の世代に何を残せるかを問うこと。\n\n哲学者ニーチェは「生きる意味を問う前に、むしろあなた自身が『答え』なのだ」と言いました。散歩中の会話から深い思索まで、この問い自体があなたを成長させるきっかけになるかもしれません。",
        "refusal": null,
        "reasoning": null
      }
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 486,
    "total_tokens": 496,
    "prompt_tokens_details": null
  }
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ストリーミング

こちらの方が参考になりそう。

https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses&lang=javascript

main.ts
import OpenAI from "openai";

const openai = new OpenAI({
	baseURL: "https://openrouter.ai/api/v1",
	apiKey: process.env.OPENROUTER_API_KEY,
});

async function main() {
	const stream = await openai.chat.completions.create({
		model: "deepseek/deepseek-chat-v3-0324:free",
		messages: [
			{
				role: "user",
				content: "人生の意味は何ですか?",
			},
		],
		stream: true,
	});

	for await (const event of stream) {
		console.log(JSON.stringify(event, null, 2));
	}
}

main().catch((error) => {
	console.error(error);
});
コンソール出力(一部)
{
  "id": "gen-1746608696-b9kAgB9F74AgsH1YPjOy",
  "provider": "Chutes",
  "model": "deepseek/deepseek-chat-v3-0324:free",
  "object": "chat.completion.chunk",
  "created": 1746608696,
  "choices": [
    {
      "index": 0,
      "delta": {
        "role": "assistant",
        "content": ""
      },
      "finish_reason": null,
      "native_finish_reason": null,
      "logprobs": null
    }
  ]
}
{
  "id": "gen-1746608696-b9kAgB9F74AgsH1YPjOy",
  "provider": "Chutes",
  "model": "deepseek/deepseek-chat-v3-0324:free",
  "object": "chat.completion.chunk",
  "created": 1746608696,
  "choices": [
    {
      "index": 0,
      "delta": {
        "role": "assistant",
        "content": "「"
      },
      "finish_reason": null,
      "native_finish_reason": null,
      "logprobs": null
    }
  ]
}
{
  "id": "gen-1746608696-b9kAgB9F74AgsH1YPjOy",
  "provider": "Chutes",
  "model": "deepseek/deepseek-chat-v3-0324:free",
  "object": "chat.completion.chunk",
  "created": 1746608696,
  "choices": [
    {
      "index": 0,
      "delta": {
        "role": "assistant",
        "content": "人生"
      },
      "finish_reason": null,
      "native_finish_reason": null,
      "logprobs": null
    }
  ]
}

...

{
  "id": "gen-1746608696-b9kAgB9F74AgsH1YPjOy",
  "provider": "Chutes",
  "model": "deepseek/deepseek-chat-v3-0324:free",
  "object": "chat.completion.chunk",
  "created": 1746608696,
  "choices": [
    {
      "index": 0,
      "delta": {
        "role": "assistant",
        "content": ""
      },
      "finish_reason": "stop",
      "native_finish_reason": "stop",
      "logprobs": null
    }
  ]
}
{
  "id": "gen-1746608696-b9kAgB9F74AgsH1YPjOy",
  "provider": "Chutes",
  "model": "deepseek/deepseek-chat-v3-0324:free",
  "object": "chat.completion.chunk",
  "created": 1746608696,
  "choices": [
    {
      "index": 0,
      "delta": {
        "role": "assistant",
        "content": ""
      },
      "finish_reason": null,
      "native_finish_reason": null,
      "logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 542,
    "total_tokens": 552,
    "prompt_tokens_details": null
  }
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

LangChain.js を使う

コマンド
pnpm add langchain @langchain/core
pnpm remove @langchain/deepseek

https://js.langchain.com/docs/integrations/chat/

ざっと見たところ OpenRouter はないので、DeepSeek などを直接使う必要があるようだ。

https://js.langchain.com/docs/integrations/chat/deepseek/

main.ts
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { ChatDeepSeek } from "@langchain/deepseek";

async function main() {
	const model = new ChatDeepSeek({
		model: "deepseek-chat",
		temperature: 0.8,
		streaming: true,
		apiKey: process.env.DEEPSEEK_API_KEY,
	});

	const messages = [
		new SystemMessage("Translate the following from Japanese into English"),
		new HumanMessage(
			"娘:お父ちゃん YouTube 見ていい? 父:どうしようかな。 娘:「どうしようかな」じゃない!",
		),
	];

	const stream = await model.stream(messages);

	for await (const message of stream) {
		if (!Array.isArray(message.content)) {
			process.stdout.write(message.content);
		}
	}
}

main().catch((error) => {
	console.error(error);
});
コンソール出力
Daughter: Dad, can I watch YouTube?  
Father: Hmm, let me think about it.  
Daughter: "Let me think about it" isn't an answer!  

(Alternatively, for a more natural English flow:)  

Daughter: Dad, can I watch YouTube?  
Father: Hmm, should I say yes?  
Daughter: "Should I say yes?" isn't an answer!  

(The daughter is playfully frustrated with her dad's non-committal response.)%

LangChain.js では OpenRouter は使えないようだ。

Python 版だと使えそうな感じ、やはり LangChain は Python で使う方が安全そうだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

AI SDK で使う

コマンド
pnpm add @openrouter/ai-sdk-provider ai zod
main.ts
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { generateText } from "ai";

async function main() {
	const openrouter = createOpenRouter({
		apiKey: process.env.OPENROUTER_API_KEY,
	});

	const { text } = await generateText({
		model: openrouter.chat("deepseek/deepseek-chat-v3-0324:free"),
		prompt: "OpenRouter とは何ですか?",
	});

	console.log(text);
}

main().catch((err) => console.error(err));
コンソール出力
**OpenRouter** は、複数のAIモデル(GPT-4、Claude、Gemini、Mistralなど)を1つのAPIで利用できる**AIモデル統合プラットフォーム**です。開発者や一般ユーザーが、さまざまなプロバイダーの最先端AIを簡単に比較・切り替えながら利用できるように設計されています。

### 主な特徴
1. **マルチモデル対応**  
   - OpenAI、Anthropic、Google DeepMind、Mistralなどの主要モデルを統合。
   - オープンソースモデル(Llama 3など)も利用可能。

2. **統一API**  
   - 異なるプロバイダーのモデルを、同じ形式のAPIリクエストで呼び出せます。

3. **コスト比較・最適化**  
   - モデルごとの価格をリアルタイムで表示し、コスト効率の良い選択が可能。

4. **ユーザーフレンドリー**  
   - ウェブUIで直接チャット可能([OpenRouter Web](https://openrouter.ai/))。
   - 課金管理や使用量分析ツールを提供。

5. **プライバシーオプション**  
   - データ収集をオフにできるモデルもあり。

### 主な用途
- 開発者が複数AIを比較検証する場合
- コストや性能のバランスで最適なモデルを選びたい時
- 特定プロバイダーに依存しないAIアプリ構築

### 料金
- モデルごとに従量課金(例: GPT-4は$0.06/1000トークン)。
- 一部モデルは無料利用可能(Mistral 7Bなど)。

OpenRouterは「AIモデルの比較サイト+統合API」と考えると分かりやすいでしょう。プロバイダーごとに個別のAPIキーを管理する手間が省け、AIの性能/価格を横断的に試せる点が強みです。

詳細は公式サイト: [https://openrouter.ai](https://openrouter.ai) をご確認ください。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ストリーミング版

main.ts
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { streamText } from "ai";

async function main() {
  const openrouter = createOpenRouter({
    apiKey: process.env.OPENROUTER_API_KEY,
  });

  const { textStream } = streamText({
    model: openrouter.chat("deepseek/deepseek-chat-v3-0324:free"),
    prompt: "OpenRouter とは何ですか?",
  });

  for await (const text of textStream) {
    process.stdout.write(text);
  }
}

main().catch((err) => console.error(err));
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

使ってみた感想

OpenAI SDK と AI SDK が使いやすかった。

AI SDK の方が LLM プロバイダーを変えやすいので、その点で優位性があるように思える。

他にも色々な使い方があるので試したいが、他にも遊びたいことがあるのでまたの機会にしよう。

このスクラップは4ヶ月前にクローズされました