コンテンツにスキップ

エージェント

wippy/agent モジュールは、ツール使用、ストリーミング、デリゲーション、トレイト、メモリを備えた AI エージェントを構築するためのフレームワークを提供します。エージェントは宣言的に定義され、コンテキスト/ランナーパターンで実行されます。

プロジェクトにモジュールを追加します:

Terminal window
wippy add wippy/agent
wippy install

エージェントモジュールには wippy/llm とプロセスホストが必要です。両方の依存関係を宣言します:

version: "1.0"
namespace: app
entries:
- name: os_env
kind: env.storage.os
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: dep.llm
kind: ns.dependency
component: wippy/llm
version: "*"
parameters:
- name: env_storage
value: app:os_env
- name: process_host
value: app:processes
- name: dep.agent
kind: ns.dependency
component: wippy/agent
version: "*"
parameters:
- name: process_host
value: app:processes

エージェントは meta.type: agent.gen1 を持つレジストリエントリです:

entries:
- name: assistant
kind: registry.entry
meta:
type: agent.gen1
name: assistant
title: Assistant
comment: A helpful chat assistant
prompt: |
You are a helpful assistant. Be concise and direct.
Answer questions clearly.
model: gpt-4o
max_tokens: 1024
temperature: 0.7
フィールド説明
meta.typestringagent.gen1 である必要があります
meta.namestringエージェント識別子
promptstringシステムプロンプト
modelstringモデル名またはクラス
max_tokensnumber最大出力トークン数(デフォルト 512
temperaturenumberサンプリング温度(デフォルト 0;範囲はプロバイダ依存)
thinking_effortnumber> 0 の場合のみモデルに転送(プロバイダ定義のスケール)
toolsarrayツールレジストリ ID
traitsarrayトレイト参照
delegatesarrayデリゲートエージェント参照
memoryarray静的メモリ項目(文字列)
memory_contracttable動的メモリ設定

エージェントコンテキストはメインのエントリポイントです。コンテキストを作成し、必要に応じて設定してから、エージェントをロードします:

imports:
agent_context: wippy.agent:context
local agent_context = require("agent_context")
local ctx = agent_context.new()
local runner, err = ctx:load_agent("app:assistant")
if err then
error("Failed to load agent: " .. tostring(err))
end
メソッド説明
agent_context.new(options?)新しいコンテキストを作成
:add_tools(specs)実行時にツールを追加
:add_delegates(specs)デリゲートエージェントを追加
:configure_delegate_tools(config)デリゲートをツールとして公開する方法を設定
:set_memory_contract(config)動的メモリを設定
:set_context_merger(fn)実行時コンテキスト更新をマージする関数を提供
:update_context(updates)実行時コンテキストを更新
:load_agent(spec_or_id, options?)エージェントをロードしてコンパイル、ランナーを返す
:switch_to_agent(id, options?)別のエージェントに切り替え、(boolean, string?) を返す
:switch_to_model(name)現在のエージェントのモデルを変更、(boolean, string?) を返す
:get_current_agent()現在のランナーを取得
:get_config()コンテキスト設定の要約を返す
local ctx = agent_context.new({
context = { session_id = "abc", user_id = "u1" },
delegate_tools = { enabled = true },
enable_cache = true,
})
オプション説明
contextツールとデリゲートに転送されるベース実行時コンテキスト
delegate_toolsデフォルトのデリゲートツール設定(configure_delegate_tools で上書き可能)
enable_cacheプロンプトキャッシュマーカーを有効化(Claude モデル)。デフォルトは true

インラインスペックによるロード

Section titled “インラインスペックによるロード”

レジストリエントリなしでエージェントをロードします:

local runner, err = ctx:load_agent({
id = "inline-agent",
name = "helper",
prompt = "You are a helpful assistant.",
model = "gpt-4o",
max_tokens = 1024,
tools = { "app.tools:search" },
})

ランナーは単一の推論ステップを実行します。会話を含むプロンプトビルダーを渡します:

local prompt = require("prompt")
local conversation = prompt.new()
conversation:add_user("What is the capital of France?")
local response, err = runner:step(conversation)
if err then
error(tostring(err))
end
print(response.result)
local response, err = runner:step(conversation, {
context = { session_id = "abc" },
stream_target = { reply_to = process.pid(), topic = "stream" },
tool_call = "auto",
})
オプション説明
contexttableエージェントコンテキストにマージされる実行時コンテキスト
stream_targettableストリーミング: { reply_to, topic }
tool_callstring"auto""any""none" またはツール名
フィールド説明
resultstring生成されたテキスト
tokenstableトークン使用量
finish_reasonstring停止理由
tool_callstable?実行すべきツール呼び出し
delegate_callstable?デリゲート呼び出し
local stats = runner:get_stats()
-- stats.id, stats.name, stats.total_tokens

ツールは meta.type: tool を持つ function.lua エントリです。別の _index.yaml で定義します:

version: "1.0"
namespace: app.tools
entries:
- name: calculate
kind: function.lua
meta:
type: tool
title: Calculate
input_schema: |
{
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Math expression to evaluate"
}
},
"required": ["expression"],
"additionalProperties": false
}
llm_alias: calculate
llm_description: Evaluate a mathematical expression.
source: file://calculate.lua
modules: [expr]
method: handler
local expr = require("expr")
local function handler(args)
local result, err = expr.eval(args.expression)
if err then
return { error = tostring(err) }
end
return { result = result }
end
return { handler = handler }
フィールド説明
meta.typestringtool である必要があります
meta.input_schemastring/tableツール引数の JSON Schema
meta.llm_aliasstringLLM に公開される名前
meta.llm_descriptionstringLLM に公開される説明
meta.exclusivebooleantrue の場合、並行するツール呼び出しをキャンセル

エージェント定義にツールレジストリ ID をリストします:

- name: assistant
kind: registry.entry
meta:
type: agent.gen1
name: assistant
prompt: You are a helpful assistant with tools.
model: gpt-4o
max_tokens: 1024
tools:
- app.tools:calculate
- app.tools:search
- app.tools:* # wildcard: all tools in namespace

カスタムエイリアスとコンテキストを指定してツールを参照することもできます:

tools:
- id: app.tools:search
alias: web_search
context:
api_key: "${SEARCH_API_KEY}"

エージェントステップが tool_calls を返した場合、それを実行して結果をフィードバックします:

local json = require("json")
local funcs = require("funcs")
local function execute_and_continue(runner, conversation)
while true do
local response, err = runner:step(conversation)
if err then return nil, err end
local tool_calls = response.tool_calls
if not tool_calls or #tool_calls == 0 then
return response.result, nil
end
for _, tc in ipairs(tool_calls) do
local result, call_err = funcs.call(tc.registry_id, tc.arguments)
local result_str
if call_err then
result_str = json.encode({ error = tostring(call_err) })
else
result_str = json.encode(result)
end
conversation:add_function_call(tc.name, tc.arguments, tc.id)
conversation:add_function_result(tc.name, result_str, tc.id)
end
end
end
フィールド説明
idstring一意の呼び出し識別子
namestringツール名(エイリアスまたは llm_alias)
argumentstableパース済み引数
registry_idstringfuncs.call() 用の完全なレジストリ ID
ツールの実行には funcs.call(tc.registry_id, tc.arguments) を使用します。registry_id フィールドは、レジストリ内のツールのエントリに直接対応します。

stream_target を使用してエージェントのレスポンスをリアルタイムでストリーミングします:

local TOPIC = "agent_stream"
local function stream_step(runner, conversation)
local stream_ch = process.listen(TOPIC)
local done_ch = channel.new(1)
coroutine.spawn(function()
local response, err = runner:step(conversation, {
stream_target = {
reply_to = process.pid(),
topic = TOPIC,
},
})
done_ch:send({ response = response, err = err })
end)
local full_text = ""
while true do
local result = channel.select({
stream_ch:case_receive(),
done_ch:case_receive(),
})
if not result.ok then break end
if result.channel == done_ch then
process.unlisten(stream_ch)
local r = result.value
return full_text, r.response, r.err
end
local chunk = result.value
if chunk.type == "chunk" then
io.write(chunk.content or "")
full_text = full_text .. (chunk.content or "")
elseif chunk.type == "done" then
-- wait for the step to complete
local r, ok = done_ch:receive()
process.unlisten(stream_ch)
if ok and r then
return full_text, r.response, r.err
end
return full_text, nil, nil
end
end
process.unlisten(stream_ch)
return full_text, nil, nil
end

ストリームは直接 LLM ストリーミングと同じチャンクタイプを使用します: "chunk""thinking""tool_call""error""done"

coroutine.spawn を使用して runner:step() を別のコルーチンで実行し、ストリームチャンクを並行して受信できるようにします。channel.select を使用してストリームチャネルと完了チャネルを多重化します。

エージェントは他のエージェントにデリゲートできます。デリゲートは親エージェントにとってツールとして表示されます:

- name: coordinator
kind: registry.entry
meta:
type: agent.gen1
name: coordinator
prompt: Route questions to the right specialist.
model: gpt-4o
max_tokens: 1024
delegates:
- id: app:code_agent
name: ask_coder
rule: for programming questions
- id: app:math_agent
name: ask_mathematician
rule: for math problems

デリゲート呼び出しは response.delegate_calls に含まれます:

local response = runner:step(conversation)
if response.delegate_calls then
for _, dc in ipairs(response.delegate_calls) do
-- dc.agent_id - target agent registry ID
-- dc.name - delegate tool name
-- dc.arguments - forwarded message
end
end

実行時にデリゲートを追加することもできます:

ctx:add_delegates({
{ id = "app:specialist", name = "ask_specialist", rule = "for domain questions" },
})

トレイトは、プロンプト、ツール、動作をエージェントに提供する再利用可能なケイパビリティです:

- name: assistant
kind: registry.entry
meta:
type: agent.gen1
name: assistant
prompt: You are a helpful assistant.
model: gpt-4o
traits:
- time_aware
- id: custom_trait
context:
key: value
トレイト説明
time_aware現在の日付と時刻をプロンプトに注入

time_aware トレイトはコンテキストオプションを受け付けます:

traits:
- id: time_aware
context:
timezone: America/New_York
time_interval: 15

トレイトは meta.type: agent.trait を持つレジストリエントリです。以下を提供できます:

  • prompt - システムプロンプトに追加される静的テキスト
  • build_func_id - コンパイル時に呼び出され、ツール、プロンプト、デリゲートを提供する関数
  • prompt_func_id - 各ステップで呼び出され、動的コンテンツを注入する関数
  • step_func_id - 各ステップで副作用のために呼び出される関数

システムプロンプトに追加されるシンプルなメモリ項目:

- name: assistant
kind: registry.entry
meta:
type: agent.gen1
name: assistant
prompt: You are a helpful assistant.
model: gpt-4o
memory:
- "User prefers concise answers"
- "Always cite sources when possible"

外部ソースからの動的メモリ呼び出しを設定します:

memory_contract:
implementation_id: app:memory_store
context:
user_id: "${user_id}"
options:
max_items: 3
max_length: 1000
recall_cooldown: 1
min_conversation_length: 2

メモリコントラクトは runner:step() の実行中に呼び出され、会話コンテキストに基づいて関連する項目を呼び出します。結果は開発者メッセージとして注入されます。

オプションデフォルト説明
max_items31回の呼び出しあたりの最大メモリ項目数
max_length1000最大合計文字数
recall_cooldown1呼び出し間の最小ステップ数
min_conversation_length2最初の呼び出しまでの最小会話ターン数

load_agent() が文字列識別子を受け取ると、まず wippy.agent:resolver コントラクトによる解決を試みます。リゾルバーがバインドされていないか、リゾルバーが nil を返した場合、レジストリルックアップにフォールバックします。

これにより、アプリケーションはデータベースからエージェント定義をロードするなど、カスタムのエージェント解決を実装できます。

リゾルバー関数を定義し、コントラクトにバインドします:

entries:
- name: agent_resolver.resolve
kind: function.lua
source: file://agent_resolver.lua
method: resolve
modules:
- logger
imports:
agent_registry: wippy.agent.discovery:registry
- name: agent_resolver_binding
kind: contract.binding
contracts:
- contract: wippy.agent:resolver
default: true
methods:
resolve: app:agent_resolver.resolve

リゾルバーは { agent_id = "..." } を受け取り、エージェントスペックテーブルまたは nil を返します:

local agent_registry = require("agent_registry")
local CUSTOM_PREFIX = "custom:"
function resolve(args)
local agent_id = args.agent_id
if not agent_id then
return nil, "agent_id is required"
end
if agent_id:sub(1, #CUSTOM_PREFIX) == CUSTOM_PREFIX then
local id = agent_id:sub(#CUSTOM_PREFIX + 1)
-- load from database, config file, or any other source
return {
id = agent_id,
name = "custom-agent",
prompt = "You are a custom agent.",
model = "class:balanced",
max_tokens = 1024,
tools = {},
}
end
-- fall back to registry
local spec, err = agent_registry.get_by_id(agent_id)
if not spec then
spec, err = agent_registry.get_by_name(agent_id)
end
return spec, err
end
return {
resolve = resolve,
}
  1. wippy.agent:resolver コントラクトを試行(バインドされている場合)
  2. ID によるレジストリルックアップを試行
  3. 名前によるレジストリルックアップを試行
  4. 見つからない場合はエラーを返す

このパターンにより、エージェントがユーザーごとまたはワークスペースごとに設定され、フレームワークのレジストリ外に保存されるマルチテナントアプリケーションが可能になります。