mcpField
L
line-voice-agent

line-voice-agent

github.com/cartesia-aiAgent 工作流6112 次安装10 周前更新
代码仓库

关于 line-voice-agent

line-voice-agent 是使用 Cartesia Line SDK 编写语音 Agent 的指南,面向构建和部署电话或应用语音交互的开发者。内容涵盖 LLM 配置、工具模式、多 Agent 工作流及部署流程。

主要功能

  1. 01Python 语音 Agent 开发
  2. 02LLM 提供方配置
  3. 03内置通话与搜索工具
  4. 04自定义工具和 Agent 交接
  5. 05Cartesia CLI 部署
  6. 06MCP 服务工具接入

适用场景

  1. 01构建电话语音 Agent
  2. 02为语音 Agent 添加 API 或数据库工具
  3. 03配置多 Agent 通话交接
  4. 04将 MCP 服务提供给 LLM

MCP 相关

Line SDK 的 mcp_tool 可将远程 HTTP/SSE 或本地 stdio MCP 服务暴露给 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 语音 Agent 指南

使用 Cartesia Line SDK 构建可用于生产环境的语音 Agent。本指南介绍 Agent 的创建、工具模式、多 Agent 工作流和大模型提供商配置。

Line 的工作方式

Line 是 Cartesia 的语音 Agent 部署平台。你使用 Line SDK 编写 Python Agent 代码,通过 cartesia CLI 将其部署到 Cartesia 的托管云,Cartesia 会托管并自动扩缩容。Cartesia 负责 STT(Ink)、TTS(Sonic)、电话集成和音频编排。每个 Agent 同一时间只能有一个部署处于活动状态;部署完成后,你的 Agent 会自动接听来电。

┌─────────────────────────────────────────────────────────────────┐
│                     Cartesia Line Platform                       │
│  ┌──────────┐    ┌──────────────┐    ┌──────────┐              │
│  │   Ink    │───▶│  Your Agent  │───▶│  Sonic   │              │
│  │  (STT)   │    │  (Line SDK)  │    │  (TTS)   │              │
│  └──────────┘    └──────────────┘    └──────────┘              │
│       ▲                                    │                    │
│       │         Audio Orchestration        │                    │
│       └────────────────────────────────────┘                    │
└─────────────────────────────────────────────────────────────────┘
        ▲                                    │
        │            WebSocket               ▼
┌───────┴────────────────────────────────────┴───────┐
│              Client (Phone / Web / Mobile)          │
└─────────────────────────────────────────────────────┘

你的代码负责:

  • 大模型推理和对话流程
  • 工具执行(API 调用、数据库查询)
  • 多 Agent 协调和交接

Cartesia 负责:

  • 语音转文本(Ink)
  • 文本转语音(Sonic)
  • 实时音频流
  • 轮流发言和打断检测
  • 部署和自动扩缩容

音频输入选项:

前提条件

  • Python 3.10+ 和 uv(推荐的包管理器)
  • Cartesia API 密钥 — 在 play.cartesia.ai/keys 获取(CLI 和部署时使用)
  • 大模型 API 密钥 — 用于 Agent 调用的大模型提供商(例如 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. 编写 Agent 代码

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 Agent 项目都必须包含:

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 声明部署元数据、本地服务地址,以及 Agent 所需的环境变量:

[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 封装大模型提供商的主要 Agent 类:

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

用于配置 Agent 行为和大模型采样的选项:

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

结束当前通话。调用此工具前,请让大模型先向用户道别。

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

搜索网络以获取实时信息。如果大模型支持原生网络搜索,则使用该功能;否则回退到 DuckDuckGo。

# Default settings
tools=[web_search]

# Custom settings
tools=[web_search(search_context_size="high")]  # "low", "medium", "high"
knowledge_base

通过自然语言查询,从 Agent 的知识库中查找信息。筛选条件、top_k 和 timeout_s 在构造时固定,大模型只能选择查询字符串。

# 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 服务。需要 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")]

大模型调用该工具时,不传参数即可列出可用工具;也可以传入 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。

自定义工具类型

适用于不同使用场景的三种工具范式:

类型装饰器使用场景结果处理
Loopback@loopback_toolAPI 调用、数据库查询将结果返回给 LLM
Passthrough@passthrough_tool结束通话、转接、DTMF绕过 LLM,直接发送给用户
Handoff@handoff_tool多 Agent 工作流将控制权转交给另一个 Agent
工具类型决策树
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"]
  • 工具描述来自文档字符串
  • 可选参数需要默认值(不能仅使用 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 工具

将控制权转交给另一个 Agent。请参阅多 Agent 工作流。

上下文管理

LlmAgent 提供一个 history 对象,用于注入和转换 LLM 所见的对话历史。

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(...)。

模型选择策略

主对话 Agent 使用 FAST 模型:

  • gemini/gemini-2.5-flash-preview-09-2025(推荐)
  • anthropic/claude-haiku-4-5-20251001
  • gpt-5-nano

仅通过后台工具调用使用 POWERFUL 模型,处理复杂推理:

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

这种模式既能让对话保持响应迅速,又能在需要时使用深度推理。实现方法请参阅高级模式中的双层 Agent 模式。

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

常见模式

使用自定义工具的 Agent
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="" 设置为让 Agent 等待用户发言:

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 工具 - 如果未包含该工具(或类似的自定义工具),Agent 就无法自行结束通话,只能等待用户挂断

  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 描述 - 大模型需要参数描述:

    # 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. 在主对话中使用速度较慢的模型 - 主 Agent 使用速度较快的模型(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 Agent 时,请参考 cartesia-ai/line 仓库中的以下示例文件:

  • examples/basic_chat/main.py - 最简单的 Agent 模式(web_search)
  • examples/form_filler/ - 带状态的回环工具
  • examples/chat_supervisor/main.py - 使用双层模型策略的后台工具
  • examples/transfer_agent/main.py - 多 Agent 交接
  • examples/transfer_phone_call/main.py - IVR 导航和电话转接
  • examples/guardrails_wrapper/ - 为 Agent 添加护栏包装
  • examples/sales_with_leads/ - 有状态的潜在客户信息提取与调研
  • examples/echo/tools.py - 自定义交接工具
  • example_integrations/ - Exa、Tavily、Cerebras、Browserbase 集成

相关 Cartesia 技能

对于直接 HTTP/WebSocket 集成(在自有后端中使用 TTS/STT/语音、SDK、可选 MCP)——而非 Line 部署,请使用此仓库中的 cartesia-api。

评论

支持 Markdown