line-voice-agent について
line-voice-agentは、Cartesia Line SDKで音声エージェントを開発するためのガイドです。電話やアプリでの音声対話を作る開発者向けに、LLM設定、ツールの設計、マルチエージェント構成、デプロイを説明します。
主な機能
- 01Pythonによる音声エージェント開発
- 02LLMプロバイダー設定
- 03通話・検索用の組み込みツール
- 04カスタムツールとエージェント間の引き継ぎ
- 05Cartesia CLIによるデプロイ
- 06MCPサーバーツールへの接続
活用シーン
- 01電話音声エージェントの構築
- 02音声エージェントへのAPI・データベースツール追加
- 03複数エージェント間の通話引き継ぎ設定
- 04LLMからのMCPサーバー利用
MCP 対応
Line SDKのmcp_toolは、リモートHTTP/SSEまたはローカルstdioのMCPサーバーをLLMに公開します。LLMはツール一覧を取得し、ツールを呼び出せます。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cartesia-ai/ skills --skill line-voice-agentインストール後、Claude Code で/line-voice-agentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced-patterns.mdcalls-api.mdmulti-agent-workflows.mdtool-patterns.mdtroubleshooting.md詳細
Line SDK 音声エージェントガイド
Cartesia Line SDK で本番環境向けの音声エージェントを構築します。このガイドでは、エージェントの作成、ツールのパターン、マルチエージェントのワークフロー、LLM プロバイダーの設定について説明します。
Line の仕組み
Line は Cartesia の音声エージェントデプロイプラットフォームです。Line SDK を使って Python でエージェントコードを記述し、cartesia CLI 経由で Cartesia のマネージドクラウドにデプロイすると、Cartesia が自動スケーリングでホストします。Cartesia は STT(Ink)、TTS(Sonic)、電話接続、音声オーケストレーションを処理します。エージェントごとに有効なデプロイは一度に 1 つだけです。デプロイすると、エージェントは自動的に通話を受け付けます。
┌─────────────────────────────────────────────────────────────────┐
│ Cartesia Line Platform │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Ink │───▶│ Your Agent │───▶│ Sonic │ │
│ │ (STT) │ │ (Line SDK) │ │ (TTS) │ │
│ └──────────┘ └──────────────┘ └──────────┘ │
│ ▲ │ │
│ │ Audio Orchestration │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▲ │
│ WebSocket ▼
┌───────┴────────────────────────────────────┴───────┐
│ Client (Phone / Web / Mobile) │
└─────────────────────────────────────────────────────┘
コードで処理する内容:
- LLM の推論と会話の流れ
- ツールの実行(API 呼び出し、データベース検索)
- マルチエージェントの連携と引き継ぎ
Cartesia が処理する内容:
- 音声テキスト変換(Ink)
- テキスト音声変換(Sonic)
- リアルタイム音声ストリーミング
- 発話交替と割り込みの検出
- デプロイと自動スケーリング
音声入力のオプション:
- Cartesia Telephony - マネージド電話番号
- Calls API - ウェブアプリ、モバイルアプリ、カスタム電話接続
前提条件
- Python 3.10+ と uv(推奨パッケージマネージャー)
- Cartesia API キー — play.cartesia.ai/keys で取得します(CLI とデプロイで使用)
- LLM API キー — エージェントが呼び出す LLM プロバイダー用(例:
ANTHROPIC_API_KEY、OPENAI_API_KEY、GEMINI_API_KEY) - Cartesia CLI — 次のコマンドでインストールします:
curl -fsSL https://cartesia.sh | sh
Cartesia CLI リファレンス
# Authentication
cartesia auth login # Login with Cartesia API key
cartesia auth status # Check auth status
# Project Setup
cartesia create [project-name] # Create project from template
cartesia init # Link existing directory to an agent
# Local Development
cartesia chat <port> # Chat with local agent (text mode)
# Deployment
cartesia deploy # Deploy to Cartesia cloud
cartesia status # Check deployment status
# Environment Variables (encrypted, stored on Cartesia)
cartesia env set KEY=VALUE # Set a single env var
cartesia env set --from .env # Import all vars from .env file
cartesia env rm <name> # Remove an env var
# Agents & Calls
cartesia agents ls # List all agents
cartesia deployments ls # List deployments
cartesia call <phone> [agent-id] # Make outbound call
コマンドの完全なリファレンス:docs.cartesia.ai/line/cli。
クイックスタート
1. プロジェクトの作成
cartesia auth login
cartesia create my-agent
cd my-agent
2. エージェントコードの記述
main.py:
import os
from line.llm_agent import LlmAgent, LlmConfig, end_call
from line.voice_agent_app import AgentEnv, CallRequest, VoiceAgentApp
async def get_agent(env: AgentEnv, call_request: CallRequest):
return LlmAgent(
model="anthropic/claude-haiku-4-5-20251001",
api_key=os.getenv("ANTHROPIC_API_KEY"),
tools=[end_call],
config=LlmConfig(
system_prompt="You are a helpful voice assistant.",
introduction="Hello! How can I help you today?",
),
)
app = VoiceAgentApp(get_agent=get_agent)
if __name__ == "__main__":
app.run()
3. ローカルでのテスト
ANTHROPIC_API_KEY=your-key python main.py
cartesia chat 8000 # Text chat with your running agent
4. デプロイ
cartesia env set ANTHROPIC_API_KEY=your-key # Encrypted, stored on Cartesia
cartesia deploy
cartesia status # Verify deployment is active
5. 通話の発信
cartesia call +1234567890 # Outbound call via CLI
または、Cartesia ダッシュボードから通話を開始します。
プロジェクト構成
すべての Line エージェントプロジェクトには、次のファイルが必須です:
my_agent/
├── main.py # VoiceAgentApp entry point (REQUIRED)
├── cartesia.toml # Deployment config, created by cartesia init or cartesia create (REQUIRED)
└── pyproject.toml # Dependencies: cartesia-line
cartesia.toml では、デプロイのメタデータ、ローカルサーバーのアドレス、エージェントが必要とする環境変数を宣言します:
[cartesia]
name = "My Agent"
description = "What this agent does"
version = "0.1.0"
[cartesia.server]
port = 8000
host = "0.0.0.0"
[cartesia.environment]
required_vars = ["ANTHROPIC_API_KEY"]
コアコンセプト
LlmAgent
LiteLLM 経由で LLM プロバイダーをラップする、メインのエージェントクラスです:
from line.llm_agent import LlmAgent, LlmConfig
agent = LlmAgent(
model="gemini/gemini-2.5-flash-preview-09-2025", # LiteLLM model string
api_key=os.getenv("GEMINI_API_KEY"), # Provider API key
tools=[end_call, my_custom_tool], # List of tools
config=LlmConfig(...), # Agent configuration
max_tool_iterations=10, # Max tool call loops (default: 10)
backend=None, # Optional provider backend override
)
LlmConfig
エージェントの動作と LLM のサンプリングを設定します:
from line.llm_agent import LlmConfig
config = LlmConfig(
# Agent behavior
system_prompt="You are a helpful assistant.",
introduction="Hello! How can I help?", # Set to "" to wait for user first
# Sampling parameters (optional)
temperature=0.7,
max_tokens=1024,
top_p=0.9,
stop=["\n\n"],
seed=42,
presence_penalty=0.0,
frequency_penalty=0.0,
# Reasoning models only: "none" | "minimal" | "low" | "medium" | "high"
reasoning_effort="low",
# Resilience (optional)
num_retries=2, # Default: 2
timeout=30.0,
fallbacks=["gpt-5-nano"], # Fallback models
# Advanced (optional)
strict_tool_schemas=True, # Default: True
extra={}, # Provider-specific pass-through kwargs to LiteLLM
)
reasoning_effortはモデルに応じて検証されます。推論をサポートしていないモデルに渡すと、ValueErrorが発生します。推論を行わないモデルでは"none"を使用するか、省略してください。
CallRequest からの動的設定
LlmConfig.from_call_request() を使って、着信した通話から設定を取得します:
async def get_agent(env: AgentEnv, call_request: CallRequest):
return LlmAgent(
model="anthropic/claude-sonnet-4-5",
api_key=os.getenv("ANTHROPIC_API_KEY"),
tools=[end_call],
config=LlmConfig.from_call_request(
call_request,
fallback_system_prompt="Default system prompt if not in request.",
fallback_introduction="Default introduction if not in request.",
temperature=0.7, # Additional LlmConfig options
),
)
優先順位:CallRequest の値 > フォールバック引数 > SDK のデフォルト
VoiceAgentApp
HTTP エンドポイントと WebSocket 接続を管理するアプリケーションの基盤です:
from line.voice_agent_app import VoiceAgentApp, AgentEnv, CallRequest
async def get_agent(env: AgentEnv, call_request: CallRequest):
# env.loop - asyncio event loop
# call_request.call_id - unique call identifier
# call_request.agent.system_prompt - from request
# call_request.agent.introduction - from request
# call_request.metadata - custom metadata dict
return LlmAgent(...)
app = VoiceAgentApp(get_agent=get_agent)
app.run(host="0.0.0.0", port=8000)
組み込みツール
line.llm_agent からインポートします:
from line.llm_agent import (
end_call, send_dtmf, transfer_call, web_search,
knowledge_base, mcp_tool, http_server_tool,
)
end_call
現在の通話を終了します。これを呼び出す前に別れの挨拶をするよう、LLM に指示してください。
tools=[end_call]
# System prompt: "Say goodbye before ending the call with end_call."
send_dtmf
DTMF トーン(プッシュボタンの音)を送信します。IVR の操作に便利です。
tools=[send_dtmf]
# Buttons: "0"-"9", "*", "#" (strings, not integers!)
transfer_call
別の電話番号に転送します(E.164 形式が必要です)。
tools=[transfer_call]
# Example: +14155551234
web_search
リアルタイム情報をウェブで検索します。利用可能な場合は LLM のネイティブなウェブ検索を使用し、利用できない場合は DuckDuckGo にフォールバックします。
# Default settings
tools=[web_search]
# Custom settings
tools=[web_search(search_context_size="high")] # "low", "medium", "high"
knowledge_base
自然言語のクエリを使って、エージェントのナレッジベースから情報を検索します。
フィルター、top_k、timeout_s は構築時に固定され、LLM が選択するのはクエリ文字列のみです。
# Default behavior — no filters
tools=[knowledge_base]
# Pre-filter every retrieval, override top_k, or run as a background lookup
tools=[knowledge_base(filters={"category": "billing"}, top_k=10)]
tools=[knowledge_base(description="Look up insurance policy terms.")]
tools=[knowledge_base(is_background=True)]
呼び出す前に、情報を検索することをユーザーに伝えてください。検索には少し時間がかかる場合があります。失敗すると KnowledgeBaseError が発生します(line からインポートしてください)。
mcp_tool
Model Context Protocol サーバーを LLM に公開します。Python 3.10+ と mcp パッケージが必要です(Line の依存関係にすでに含まれています)。
# Remote HTTP/SSE server
tools=[mcp_tool(name="dmcp", server_url="https://dmcp-server.deno.dev/sse")]
# Local stdio server
tools=[mcp_tool(name="memory", command="npx -y @modelcontextprotocol/server-memory")]
LLM は引数なしでツールを呼び出して利用可能なツールを一覧表示するか、tool_name と tool_args を指定してツールを実行します。
http_server_tool
JSON スキーマから HTTP/Webhook ツールを作成できます。カスタム関数は不要です。LLM がスキーマのフィールドに値を入力し、SDK がリクエストを送信します。
constant_value を持つプロパティは LLM から隠され、すべてのリクエストに挿入されます。
auth 内の ${ENV_VAR} プレースホルダーは、ビルド時に os.environ から解決されます。
create_ticket = http_server_tool(
name="create_ticket",
description="Creates a support ticket for the caller.",
url="https://api.example.com/v1/{tenant_id}/tickets", # {param} = path variable
method="POST",
request_body_schema={
"type": "object",
"required": ["subject", "priority"],
"properties": {
"subject": {"type": "string", "description": "Short summary."},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"source": {"type": "string", "constant_value": "voice_agent"}, # hidden
},
},
query_params_schema=None, # same shape, scalar types only, for GET query params
auth={"Authorization": "Bearer ${SUPPORT_API_KEY}"},
content_type="application/json", # or "application/x-www-form-urlencoded"
timeout=5.0,
is_background=True, # default True
)
tools=[create_ticket, end_call]
LLM は常に構造化された JSON の結果を受け取ります。例:{"ok": true, "status": 201, "body": "..."} または {"ok": false, "status": 500, "error": "..."}。
注:一部の Line のドキュメント/README では
webhook_toolと記載されていますが、エクスポートされる関数名はhttp_server_toolです。
カスタムツールの種類
用途に応じた 3 つのツールパラダイム:
| 種類 | デコレーター | 用途 | 結果の処理 |
|---|---|---|---|
| Loopback | @loopback_tool | API 呼び出し、データベース検索 | 結果を LLM に返します |
| Passthrough | @passthrough_tool | 通話終了、転送、DTMF | LLM を介さず、ユーザーに送られます |
| Handoff | @handoff_tool | マルチエージェントのワークフロー | 別のエージェントに制御を移します |
ツールの種類を選ぶ判断フロー
Does the result need LLM processing?
├─ YES → @loopback_tool
│ └─ Is it long-running (>1s)? → @loopback_tool(is_background=True)
│ └─ Yield interim status, then final result
├─ NO, deterministic action → @passthrough_tool
│ └─ Yields OutputEvent objects directly (AgentSendText, AgentEndCall, etc.)
└─ Transfer to another agent → @handoff_tool or agent_as_handoff()
Loopback ツール
次の応答に役立てるため、結果を LLM に返します:
from typing import Annotated
from line.llm_agent import loopback_tool, ToolEnv
@loopback_tool
async def get_order_status(
ctx: ToolEnv,
order_id: Annotated[str, "The order ID to look up"],
) -> str:
"""Look up the current status of an order."""
order = await db.get_order(order_id)
return f"Order {order_id} status: {order.status}, ETA: {order.eta}"
パラメーターの構文:
- 最初のパラメーターは必ず
ctx: ToolEnvにします - LLM に表示するパラメーターには
Annotated[type, "description"]を使用します - ツールの説明は docstring から取得されます
- オプションのパラメーターにはデフォルト値が必要です(
Optional[T]だけでは不十分です)
@loopback_tool
async def search_products(
ctx: ToolEnv,
query: Annotated[str, "Search query"],
category: Annotated[str, "Product category"] = "all", # Optional with default
limit: Annotated[int, "Max results"] = 10,
) -> str:
"""Search the product catalog."""
...
Passthrough ツール
結果は LLM を介さず、ユーザー/システムに直接送られます:
from line.events import AgentSendText, AgentTransferCall
from line.llm_agent import passthrough_tool, ToolEnv
@passthrough_tool
async def transfer_to_support(
ctx: ToolEnv,
reason: Annotated[str, "Reason for transfer"],
):
"""Transfer the call to the support team."""
yield AgentSendText(text="Let me transfer you to our support team now.")
yield AgentTransferCall(target_phone_number="+18005551234")
出力イベントの種類(line.events より):
AgentSendText(text="...")- ユーザーにテキストを読み上げますAgentEndCall()- 通話を終了しますAgentTransferCall(target_phone_number="+1...")- 通話を転送しますAgentSendDtmf(button="5")- DTMF トーンを送信します
Handoff ツール
別のエージェントに制御を移します。マルチエージェントのワークフローを参照してください。
コンテキスト管理
LlmAgent は、LLM が参照する会話履歴を挿入および変換するための history オブジェクトを公開します。
agent = LlmAgent(model="gemini/gemini-2.5-flash-preview-09-2025", api_key=...)
# Inject a custom entry (defaults to role="user"; pass role="system" for a system note)
agent.history.add_entry("The customer's name is Alice and she has a premium account.")
# Anchor an insertion relative to an existing event
agent.history.add_entry("Reminder: stay concise.", role="system", after=some_event)
# Replace a segment of history with new events (filtering, summarization, etc.)
agent.history.update(new_events, start=first_event, end=last_event)
エントリーは遅延挿入され、ターンをまたいで保持されます。ツール内では agent.history.add_entry(...) を呼び出して、外部 API から取得したリッチなコンテキストを保持できます。
注:一部の Line の README では
agent.add_history_entry(...)/agent.set_history_processor(...)が示されています。実装されている API はagent.history.add_entry(...)とagent.history.update(...)です。
モデル選択の戦略
メインの会話エージェントには高速なモデルを使用します:
gemini/gemini-2.5-flash-preview-09-2025(推奨)anthropic/claude-haiku-4-5-20251001gpt-5-nano
複雑な推論には、バックグラウンドのツール呼び出しでのみ高性能モデルを使用します:
anthropic/claude-opus-4-5gpt-5.2
このパターンにより、必要に応じて高度な推論を利用しながら、会話の応答性を保てます。実装については、高度なパターンの 2 階層エージェントパターンを参照してください。
LLM プロバイダー
Line SDK は LiteLLM のモデル文字列を使用します。一般的な形式:
| プロバイダー | 形式 | 例 |
|---|---|---|
| OpenAI | model_name | gpt-5.2、gpt-5-nano |
| Anthropic | anthropic/model_name | anthropic/claude-sonnet-4-5、anthropic/claude-haiku-4-5-20251001 |
| Google Gemini | gemini/model_name | gemini/gemini-2.5-flash-preview-09-2025 |
| Azure OpenAI | azure/deployment_name | azure/my-deployment |
適切な API キーの環境変数を設定します:
OPENAI_API_KEYANTHROPIC_API_KEYGEMINI_API_KEYAZURE_API_KEY
全リスト:https://docs.litellm.ai/docs/providers
よくあるパターン
カスタムツールを持つエージェント
from typing import Annotated
from line.llm_agent import LlmAgent, LlmConfig, loopback_tool, end_call, ToolEnv
@loopback_tool
async def check_appointment(
ctx: ToolEnv,
date: Annotated[str, "Date in YYYY-MM-DD format"],
) -> str:
"""Check available appointment slots for a given date."""
slots = await calendar.get_available_slots(date)
return f"Available slots on {date}: {', '.join(slots)}"
@loopback_tool
async def book_appointment(
ctx: ToolEnv,
date: Annotated[str, "Date in YYYY-MM-DD format"],
time: Annotated[str, "Time in HH:MM format"],
name: Annotated[str, "Customer name"],
) -> str:
"""Book an appointment slot."""
result = await calendar.book(date, time, name)
return f"Appointment booked for {name} on {date} at {time}. Confirmation: {result.id}"
async def get_agent(env: AgentEnv, call_request: CallRequest):
return LlmAgent(
model="gemini/gemini-2.5-flash-preview-09-2025",
api_key=os.getenv("GEMINI_API_KEY"),
tools=[check_appointment, book_appointment, end_call],
config=LlmConfig(
system_prompt="""You are an appointment scheduling assistant.
Help users check availability and book appointments.
Always confirm the booking details before finalizing.""",
introduction="Hi! I can help you schedule an appointment. What date works for you?",
),
)
ユーザーが話し始めるまで待つ
introduction="" を設定すると、エージェントはユーザーを待機します:
config=LlmConfig(
system_prompt="You are a helpful assistant.",
introduction="", # Empty string = wait for user
)
フォーム入力パターン
音声で構造化データを収集する方法については、フォーム入力の例を参照してください。主なパターン:
@loopback_tool
async def record_answer(
ctx: ToolEnv,
answer: Annotated[str, "The user's answer"],
) -> dict:
"""Record an answer to the current question."""
# Process and validate answer
# Return next question or completion status
return {"next_question": "What is your email?", "is_complete": False}
よくある間違いと回避方法
-
end_callツールがない - 含まれていない場合(または同様のカスタムツールがない場合)、エージェントは自分で通話を終了できず、ユーザーが電話を切るまで待たなければなりません -
ツールで例外を発生させる - ユーザーに分かりやすいエラー文字列を返してください:
# BAD raise ValueError("Invalid order ID") # GOOD return "I couldn't find that order. Please check the ID and try again." -
ctxパラメーターを忘れる - 最初のパラメーターはctx: ToolEnvでなければなりません:# GOOD @loopback_tool async def my_tool(ctx: ToolEnv, order_id: Annotated[str, "Order ID"]): ... -
ハンドオフツールで
eventを忘れる - ハンドオフツールにはeventパラメーターが必須です:# GOOD @handoff_tool async def my_handoff(ctx: ToolEnv, param: Annotated[str, "desc"], event): ... -
Annotated の説明がない - LLM にはパラメーターの説明が必要です:
# GOOD async def my_tool(ctx, order_id: Annotated[str, "The order ID to look up"]): ... -
時間のかかる処理でブロックする -
is_background=Trueを使い、途中経過を返してください:@loopback_tool(is_background=True) async def slow_search(ctx: ToolEnv, query: Annotated[str, "Query"]): yield "Searching..." # Immediate feedback result = await slow_operation() yield result -
同期 API を直接使う - 同期呼び出しは
asyncio.to_thread()でラップしてください:result = await asyncio.to_thread(sync_api_call, params) -
メインの会話に低速なモデルを使う - メインエージェントには高速なモデル(haiku、flash、mini)を使い、強力なモデルはバックグラウンドツール経由でのみ使用してください。
リファレンスドキュメント
このスキル内:
- ツールパターン - ツール実装の詳細
- マルチエージェントワークフロー - ハンドオフ、ラッパー、ガードレール
- 高度なパターン - バックグラウンドツール、状態、イベント
- Calls API - Web/モバイルアプリ向けの WebSocket 統合
- トラブルシューティング - よくある問題とデバッグ
- SDK の概要 — アーキテクチャとインストール
- ツールガイド — ツールの種類の詳細
- エージェントガイド — LlmAgent、カスタムエージェント、会話ループ
- イベントリファレンス — 入力/出力イベント
- CLI リファレンス — デプロイ、環境、エージェント、通話
主なインポート
# Core
from line.llm_agent import LlmAgent, LlmConfig
from line.voice_agent_app import VoiceAgentApp, AgentEnv, CallRequest
# Built-in tools
from line.llm_agent import (
end_call, send_dtmf, transfer_call, web_search,
knowledge_base, mcp_tool, http_server_tool,
)
# Tool decorators
from line.llm_agent import loopback_tool, passthrough_tool, handoff_tool
# Tool context
from line.llm_agent import ToolEnv
# Multi-agent
from line.llm_agent import agent_as_handoff
# Knowledge base (errors / client)
from line import KnowledgeBase, KnowledgeBaseError
# Events (for passthrough/handoff tools and custom agents)
from line.events import (
AgentSendText,
AgentEndCall,
AgentTransferCall,
AgentSendDtmf,
AgentUpdateCall,
AgentSendCustom,
CustomHistoryEntry,
HistoryEvent,
)
主なリファレンスファイル
Line SDK エージェントを実装する際は、
cartesia-ai/line リポジトリ内の以下のサンプルファイルを参照してください:
examples/basic_chat/main.py- 最もシンプルなエージェントパターン(web_search)examples/form_filler/- 状態を持つループバックツールexamples/chat_supervisor/main.py- 2 層のモデル戦略を用いたバックグラウンドツールexamples/transfer_agent/main.py- マルチエージェントのハンドオフexamples/transfer_phone_call/main.py- IVR ナビゲーションと電話転送examples/guardrails_wrapper/- ガードレールによるエージェントのラップexamples/sales_with_leads/- 状態を保持したリード抽出とリサーチexamples/echo/tools.py- カスタムハンドオフツールexample_integrations/- Exa、Tavily、Cerebras、Browserbase の統合
関連する Cartesia スキル
直接 HTTP/WebSocket 統合(独自のバックエンドでの TTS/STT/音声、SDK、任意の MCP)を行う場合、Line のデプロイではなく、このリポジトリの cartesia-api を使用してください。