mcpField
L
line-voice-agent

line-voice-agent

github.com/cartesia-aiエージェントワークフロー6112 回インストール10 週間前に更新
リポジトリ

line-voice-agent について

line-voice-agentは、Cartesia Line SDKで音声エージェントを開発するためのガイドです。電話やアプリでの音声対話を作る開発者向けに、LLM設定、ツールの設計、マルチエージェント構成、デプロイを説明します。

主な機能

  1. 01Pythonによる音声エージェント開発
  2. 02LLMプロバイダー設定
  3. 03通話・検索用の組み込みツール
  4. 04カスタムツールとエージェント間の引き継ぎ
  5. 05Cartesia CLIによるデプロイ
  6. 06MCPサーバーツールへの接続

活用シーン

  1. 01電話音声エージェントの構築
  2. 02音声エージェントへのAPI・データベースツール追加
  3. 03複数エージェント間の通話引き継ぎ設定
  4. 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.md
calls-api.md
multi-agent-workflows.md
tool-patterns.md
troubleshooting.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)
  • リアルタイム音声ストリーミング
  • 発話交替と割り込みの検出
  • デプロイと自動スケーリング

音声入力のオプション:

前提条件

  • 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_toolAPI 呼び出し、データベース検索結果を LLM に返します
Passthrough@passthrough_tool通話終了、転送、DTMFLLM を介さず、ユーザーに送られます
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-20251001
  • gpt-5-nano

複雑な推論には、バックグラウンドのツール呼び出しでのみ高性能モデルを使用します:

  • anthropic/claude-opus-4-5
  • gpt-5.2

このパターンにより、必要に応じて高度な推論を利用しながら、会話の応答性を保てます。実装については、高度なパターンの 2 階層エージェントパターンを参照してください。

LLM プロバイダー

Line SDK は LiteLLM のモデル文字列を使用します。一般的な形式:

プロバイダー形式例
OpenAImodel_namegpt-5.2、gpt-5-nano
Anthropicanthropic/model_nameanthropic/claude-sonnet-4-5、anthropic/claude-haiku-4-5-20251001
Google Geminigemini/model_namegemini/gemini-2.5-flash-preview-09-2025
Azure OpenAIazure/deployment_nameazure/my-deployment

適切な API キーの環境変数を設定します:

  • OPENAI_API_KEY
  • ANTHROPIC_API_KEY
  • GEMINI_API_KEY
  • AZURE_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}

よくある間違いと回避方法

  1. end_call ツールがない - 含まれていない場合(または同様のカスタムツールがない場合)、エージェントは自分で通話を終了できず、ユーザーが電話を切るまで待たなければなりません

  2. ツールで例外を発生させる - ユーザーに分かりやすいエラー文字列を返してください:

    # BAD
    raise ValueError("Invalid order ID")
    
    # GOOD
    return "I couldn't find that order. Please check the ID and try again."
    
  3. ctx パラメーターを忘れる - 最初のパラメーターは ctx: ToolEnv でなければなりません:

    # GOOD
    @loopback_tool
    async def my_tool(ctx: ToolEnv, order_id: Annotated[str, "Order ID"]): ...
    
  4. ハンドオフツールで event を忘れる - ハンドオフツールには event パラメーターが必須です:

    # GOOD
    @handoff_tool
    async def my_handoff(ctx: ToolEnv, param: Annotated[str, "desc"], event): ...
    
  5. Annotated の説明がない - LLM にはパラメーターの説明が必要です:

    # GOOD
    async def my_tool(ctx, order_id: Annotated[str, "The order ID to look up"]): ...
    
  6. 時間のかかる処理でブロックする - 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
    
  7. 同期 API を直接使う - 同期呼び出しは asyncio.to_thread() でラップしてください:

    result = await asyncio.to_thread(sync_api_call, params)
    
  8. メインの会話に低速なモデルを使う - メインエージェントには高速なモデル(haiku、flash、mini)を使い、強力なモデルはバックグラウンドツール経由でのみ使用してください。

リファレンスドキュメント

このスキル内:

docs.cartesia.ai:

主なインポート

# 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 を使用してください。

コメント