LLM
wippy/llm モジュールは、複数のプロバイダー(OpenAI、Anthropic、Google、ローカルモデル)の大規模言語モデルを操作するための統一インターフェースを提供します。テキスト生成、ツール呼び出し、構造化出力、エンベディング、ストリーミングに対応しています。
セットアップ
プロジェクトにモジュールを追加します:
wippy add wippy/llm
wippy install
_index.yaml で依存関係を宣言します。LLM モジュールには環境ストレージ(APIキー用)とプロセスホストが必要です:
version: "1.0"
namespace: app
entries:
- name: dep.llm
kind: ns.dependency
component: wippy/llm
version: "*"
env.storage.os エントリは、OS 環境変数を LLM プロバイダーに公開します。APIキーを環境変数として設定してください(例: OPENAI_API_KEY、ANTHROPIC_API_KEY)。
テキスト生成
エントリに llm ライブラリをインポートし、generate() を呼び出します:
entries:
- name: ask
kind: function.lua
source: file://ask.lua
method: handler
imports:
llm: wippy.llm:llm
local llm = require("llm")
local function handler()
local response, err = llm.generate("What are the three laws of robotics?", {
model = "gpt-4o"
})
if err then
return nil, err
end
return response.result
end
return { handler = handler }
generate() の第1引数には、文字列プロンプト、プロンプトビルダー、またはメッセージのテーブルを指定できます。第2引数はオプションテーブルです。
生成オプション
| オプション | 型 | 説明 |
|---|---|---|
model |
string | モデル名またはクラス(必須) |
temperature |
number | ランダム性の制御、0-1 |
max_tokens |
number | 生成する最大トークン数 |
top_p |
number | Nucleus サンプリングパラメータ |
top_k |
number | Top-k フィルタリング |
thinking_effort |
number | 思考の深さ 0-100(思考機能を持つモデル) |
tools |
table | ツール定義の配列 |
tool_choice |
string | "auto"、"none"、"any"、またはツール名 |
stream |
table | ストリーミング設定: { reply_to, topic, buffer_size } |
timeout |
number | リクエストタイムアウト(秒単位、デフォルト 600) |
レスポンス構造
| フィールド | 型 | 説明 |
|---|---|---|
result |
string | 生成されたテキストコンテンツ |
tokens |
table | トークン使用量: prompt_tokens、completion_tokens、thinking_tokens、total_tokens、およびオプションの cache_read_input_tokens、cache_read_tokens、cache_creation_input_tokens、cache_write_tokens |
finish_reason |
string | 生成が停止した理由: "stop"、"length"、"tool_call"、"filtered"、"error" |
tool_calls |
table? | ツール呼び出しの配列(モデルがツールを呼び出した場合) |
metadata |
table | プロバイダー固有のメタデータ |
usage_record |
table? | 使用量追跡レコード |
プロンプトビルダー
マルチターン会話や複雑なプロンプトには、プロンプトビルダーを使用します:
imports:
llm: wippy.llm:llm
prompt: wippy.llm:prompt
local llm = require("llm")
local prompt = require("prompt")
local conversation = prompt.new()
conversation:add_system("You are a helpful assistant.")
conversation:add_user("What is the capital of France?")
local response, err = llm.generate(conversation, {
model = "gpt-4o",
temperature = 0.7,
max_tokens = 500
})
ビルダーメソッド
| メソッド | 説明 |
|---|---|
prompt.new() |
空のビルダーを作成 |
prompt.with_system(content) |
システムメッセージ付きでビルダーを作成 |
:add_system(content, meta?) |
システムメッセージを追加 |
:add_user(content, meta?) |
ユーザーメッセージを追加 |
:add_assistant(content, meta?) |
アシスタントメッセージを追加 |
:add_developer(content, meta?) |
開発者メッセージを追加 |
:add_message(role, content_parts, name?, meta?) |
ロールとコンテンツパーツを指定してメッセージを追加 |
:add_function_call(name, arguments, id?, options?) |
アシスタントからのツール呼び出しを追加(arguments は生の JSON 文字列) |
:add_function_result(name, result, id?) |
ツール実行結果を追加 |
:add_cache_marker(id?) |
キャッシュ境界をマーク(Claude モデル) |
:get_messages() |
メッセージ配列を取得 |
:build() |
llm.generate() 用の { messages = ... } テーブルを取得 |
:clone() |
ビルダーのディープコピーを作成 |
:clear() |
すべてのメッセージを削除 |
すべての add_* メソッドはチェーン呼び出しのためにビルダーを返します。
マルチターン会話
メッセージを追加してターン間のコンテキストを構築します:
local conversation = prompt.new()
conversation:add_system("You are a helpful assistant.")
-- first turn
conversation:add_user("What is Lua?")
local r1 = llm.generate(conversation, { model = "gpt-4o" })
conversation:add_assistant(r1.result)
-- second turn with full context
conversation:add_user("What makes it different from Python?")
local r2 = llm.generate(conversation, { model = "gpt-4o" })
マルチモーダルコンテンツ
テキストと画像を1つのメッセージに組み合わせます:
local conversation = prompt.new()
conversation:add_message(prompt.ROLE.USER, {
prompt.text("What's in this image?"),
prompt.image("https://example.com/photo.jpg")
})
| 関数 | 説明 |
|---|---|
prompt.text(content) |
テキストコンテンツパーツ |
prompt.image(url, mime_type?) |
URL からの画像 |
prompt.image_base64(mime_type, data) |
Base64 エンコードされた画像 |
ロール定数
| 定数 | 値 |
|---|---|
prompt.ROLE.SYSTEM |
"system" |
prompt.ROLE.USER |
"user" |
prompt.ROLE.ASSISTANT |
"assistant" |
prompt.ROLE.DEVELOPER |
"developer" |
prompt.ROLE.FUNCTION_CALL |
"function_call" |
prompt.ROLE.FUNCTION_RESULT |
"function_result" |
prompt.ROLE.CACHE_MARKER |
"cache_marker" |
クローン
オリジナルを変更せずにバリエーションを作成するには、ビルダーをクローンします:
local base = prompt.new()
base:add_system("You are a helpful assistant.")
local conv1 = base:clone()
conv1:add_user("What is AI?")
local conv2 = base:clone()
conv2:add_user("What is ML?")
ストリーミング
プロセス通信を使用してリアルタイムでレスポンスをストリーミングします。これには process.lua エントリが必要です:
local llm = require("llm")
local TOPIC = "llm_stream"
local function main()
local stream_ch, listen_err = process.listen(TOPIC)
if listen_err then
return nil, listen_err
end
local function finish(text, response, err)
local ok, cleanup_err = process.unlisten(stream_ch)
if not ok then
cleanup_err = cleanup_err or "Failed to remove LLM stream listener"
if err then
return nil, tostring(err) .. "; cleanup failed: " .. tostring(cleanup_err)
end
return nil, cleanup_err
end
if err then
return nil, err
end
return text, response
end
local self_pid, pid_err = process.pid()
if pid_err then
return finish(nil, nil, pid_err)
end
local done_ch = channel.new(1)
coroutine.spawn(function()
local response, err = llm.generate("Write a short story", {
model = "gpt-4o",
stream = {
reply_to = self_pid,
topic = TOPIC,
},
})
done_ch:send({ response = response, err = err })
end)
local full_text = ""
local generation_result = nil
local stream_done = false
local stream_err = nil
while true do
local cases = {}
if not stream_done then
table.insert(cases, stream_ch:case_receive())
end
if not generation_result then
table.insert(cases, done_ch:case_receive())
end
local result = channel.select(cases)
if not result.ok then
return finish(nil, nil, "LLM stream closed before completion")
end
if result.channel == done_ch then
generation_result = result.value
if generation_result.err then
return finish(nil, nil, generation_result.err)
end
if stream_done then
return finish(full_text, generation_result.response, stream_err)
end
else
local chunk = result.value
if chunk.type == "chunk" then
local content = chunk.content or ""
print(content)
full_text = full_text .. content
elseif chunk.type == "thinking" then
print(chunk.content or "")
elseif chunk.type == "error" then
stream_done = true
stream_err = chunk.error and chunk.error.message or "LLM stream failed"
elseif chunk.type == "done" then
stream_done = true
end
if stream_done and generation_result then
return finish(full_text, generation_result.response, stream_err)
end
end
end
end
チャンクタイプ
| タイプ | フィールド | 説明 |
|---|---|---|
"chunk" |
content |
テキストコンテンツの断片 |
"thinking" |
content |
モデルの思考プロセス |
"tool_call" |
name, arguments, id |
ツール呼び出し |
"error" |
error.message, error.type |
ストリームエラー |
"done" |
meta |
ストリーム完了 |
process.lua エントリが必要です。Wippy のプロセス通信システム(process.pid()、process.listen())を使用するためです。
ツール呼び出し
インラインスキーマとしてツールを定義し、generate() に渡します:
local llm = require("llm")
local prompt = require("prompt")
local json = require("json")
local tools = {
{
name = "get_weather",
description = "Get current weather for a location",
schema = {
type = "object",
properties = {
location = { type = "string", description = "City name" },
},
required = { "location" },
},
},
}
local conversation = prompt.new()
conversation:add_user("What's the weather in Tokyo?")
local response = llm.generate(conversation, {
model = "gpt-4o",
tools = tools,
tool_choice = "auto",
})
if response.tool_calls and #response.tool_calls > 0 then
for _, tc in ipairs(response.tool_calls) do
-- execute the tool and get a result
local result = { temperature = 22, condition = "sunny" }
-- add the exchange to the conversation
conversation:add_function_call(tc.name, json.encode(tc.arguments), tc.id)
conversation:add_function_result(tc.name, json.encode(result), tc.id)
end
-- continue generation with tool results
local final = llm.generate(conversation, { model = "gpt-4o" })
print(final.result)
end
ツール呼び出しフィールド
| フィールド | 型 | 説明 |
|---|---|---|
id |
string | 一意の呼び出し識別子 |
name |
string | ツール名 |
arguments |
table | スキーマに一致するパース済み引数 |
ツール選択
| 値 | 動作 |
|---|---|
"auto" |
モデルがツールを使用するかどうかを決定(デフォルト) |
"none" |
ツールを使用しない |
"any" |
少なくとも1つのツールを使用する必要がある |
"tool_name" |
指定されたツールを使用する必要がある |
構造化出力
スキーマに一致する検証済み JSON を生成します:
local llm = require("llm")
local schema = {
type = "object",
properties = {
name = { type = "string" },
age = { type = "number" },
hobbies = {
type = "array",
items = { type = "string" },
},
},
required = { "name", "age", "hobbies" },
additionalProperties = false,
}
local response, err = llm.structured_output(schema, "Describe a fictional character", {
model = "gpt-4o",
})
if not err then
print(response.result.name)
print(response.result.age)
end
required 配列に含める必要があります。オプションフィールドにはユニオン型を使用してください: type = {"string", "null"}。additionalProperties = false を設定してください。
モデル設定 {#model-configuration}
モデルは meta.type: llm.model を持つレジストリエントリとして定義されます:
entries:
- name: gpt-4o
kind: registry.entry
meta:
name: gpt-4o
type: llm.model
title: GPT-4o
comment: OpenAI's flagship model
capabilities:
- generate
- tool_use
- structured_output
- vision
class:
- balanced
priority: 100
max_tokens: 128000
output_tokens: 16384
pricing:
input: 2.5
output: 10
providers:
- id: wippy.llm.openai:provider
provider_model: gpt-4o
モデルエントリフィールド
| フィールド | 説明 |
|---|---|
meta.name |
API 呼び出しで使用されるモデル識別子 |
meta.type |
llm.model である必要があります |
meta.capabilities |
機能リスト: generate、tool_use、structured_output、embed、thinking、vision、caching |
meta.class |
クラスメンバーシップ: fast、balanced、reasoning など |
meta.priority |
クラスベース解決の数値優先度(高い方が優先) |
max_tokens |
最大コンテキストウィンドウ |
output_tokens |
最大出力トークン数 |
pricing |
100万トークンあたりのコスト: input、output |
providers |
id(プロバイダーエントリ)と provider_model(プロバイダー固有のモデル名)を含む配列 |
ローカルモデル
ローカルでホストされたモデル(LM Studio、Ollama)の場合、カスタム base_url を持つ別のプロバイダーエントリを定義します:
- name: local_provider
kind: registry.entry
meta:
name: ollama
type: llm.provider
title: Ollama Local
driver:
id: wippy.llm.openai:driver
options:
api_key_env: none
base_url: http://127.0.0.1:11434/v1
- name: local-llama
kind: registry.entry
meta:
name: local-llama
type: llm.model
title: Local Llama
capabilities:
- generate
max_tokens: 4096
output_tokens: 4096
pricing:
input: 0
output: 0
providers:
- id: app:local_provider
provider_model: llama-3.2
モデル解決
モデルは正確な名前、クラス、または明示的なクラスプレフィックスで参照できます:
-- exact model name
llm.generate("Hello", { model = "gpt-4o" })
-- model class (picks highest priority in that class)
llm.generate("Hello", { model = "fast" })
-- explicit class syntax
llm.generate("Hello", { model = "class:reasoning" })
解決順序:
- 正確な
meta.nameで一致 - クラス名で一致(
meta.priorityが最も高いものが優先) class:プレフィックス付きの場合、そのクラス内のみを検索
モデルディスカバリー
実行時に利用可能なモデルとその機能を照会します:
local llm = require("llm")
-- all models
local models = llm.available_models()
-- filter by capability
local tool_models = llm.available_models("tool_use")
local embed_models = llm.available_models("embed")
-- list model classes
local classes = llm.get_classes()
for _, c in ipairs(classes) do
print(c.name .. ": " .. c.title)
end
エンベディング
セマンティック検索用のベクトルエンベディングを生成します:
local llm = require("llm")
-- A single input still returns an array of vectors.
local single_response, single_err = llm.embed("The quick brown fox", {
model = "text-embedding-3-small",
dimensions = 512,
})
if single_err then
error("Embedding failed: " .. tostring(single_err))
end
local vector = single_response.result[1]
-- Multiple inputs return one vector per input.
local batch_response, batch_err = llm.embed({
"First document",
"Second document",
}, { model = "text-embedding-3-small" })
if batch_err then
error("Batch embedding failed: " .. tostring(batch_err))
end
local vectors = batch_response.result
プロバイダーステータス
作業を送信する前にプロバイダーを確認します。準備状態の確認や軽量なヘルスモニタリングに有用です:
local status, err = llm.status({
model = "gpt-4o",
})
| オプション | 説明 |
|---|---|
model |
必須。確認するモデル。 |
provider_id |
任意。モデル解決をスキップし、特定のプロバイダーを対象にします。 |
プロバイダーの StatusResponse を返します(内容はプロバイダーによって異なります)。
エラーハンドリング
エラーは第2戻り値として返されます。エラー時、第1戻り値は nil です:
local response, err = llm.generate("Hello", { model = "gpt-4o" })
if err then
print("Error: " .. tostring(err))
return
end
print(response.result)
エラータイプ
| 定数 | 説明 |
|---|---|
llm.ERROR_TYPE.INVALID_REQUEST |
不正なリクエスト |
llm.ERROR_TYPE.AUTHENTICATION |
無効な API キー |
llm.ERROR_TYPE.RATE_LIMIT |
プロバイダーのレート制限超過 |
llm.ERROR_TYPE.SERVER_ERROR |
プロバイダーのサーバーエラー |
llm.ERROR_TYPE.CONTEXT_LENGTH |
入力がコンテキストウィンドウを超過 |
llm.ERROR_TYPE.CONTENT_FILTER |
安全システムによるコンテンツフィルタリング |
llm.ERROR_TYPE.TIMEOUT |
リクエストタイムアウト |
llm.ERROR_TYPE.MODEL_ERROR |
無効または利用不可のモデル |
終了理由
| 定数 | 説明 |
|---|---|
llm.FINISH_REASON.STOP |
正常完了 |
llm.FINISH_REASON.LENGTH |
最大トークン数に到達 |
llm.FINISH_REASON.CONTENT_FILTER |
コンテンツがフィルタリングされた |
llm.FINISH_REASON.TOOL_CALL |
モデルがツール呼び出しを行った |
llm.FINISH_REASON.ERROR |
生成中にエラーが発生 |
ケイパビリティ
| 定数 | 説明 |
|---|---|
llm.CAPABILITY.GENERATE |
テキスト生成 |
llm.CAPABILITY.TOOL_USE |
ツール/関数呼び出し |
llm.CAPABILITY.STRUCTURED_OUTPUT |
JSON 構造化出力 |
llm.CAPABILITY.EMBED |
ベクトルエンベディング |
llm.CAPABILITY.THINKING |
拡張思考 |
llm.CAPABILITY.VISION |
画像理解 |
llm.CAPABILITY.CACHING |
プロンプトキャッシュ |
関連項目
- エージェント - ツール、デリゲート、メモリを備えたエージェントフレームワーク
- LLM エージェントの構築 - ステップバイステップのチュートリアル
- フレームワーク概要 - フレームワークモジュールの使用方法