从零到一:用ADK与Ollama打造你的专属本地AI客服

最近在折腾AI应用落地的朋友,估计都绕不开一个核心痛点:如何快速、低成本地构建一个可控、可定制的智能对话系统? 无论是想给自家产品加个智能助手,还是为内部团队开发一个效率工具,直接调用云端大模型API虽然方便,但总会遇到成本、数据隐私、响应延迟和模型定制化程度不足等问题。

这时候,本地化部署的优势就凸显出来了。而谷歌开源的 Agent Development Kit (ADK),恰好为我们提供了一条从“玩具级”Demo快速迈向“生产级”应用的清晰路径。它不像一些框架那样需要你从零开始造轮子,而是把智能体开发中那些繁琐的架构、编排、工具集成问题都封装好了,让你能像搭积木一样,专注于业务逻辑本身。

今天,我们就聚焦一个非常具体且实用的场景:为个人项目或中小企业,搭建一个运行在本地的AI客服原型。我们将完全使用开源方案,核心是 ADK 框架 + Ollama 本地模型。我会带你走通从环境准备、模型选择、智能体构建到多轮对话测试的完整流程,并重点分享几个我实际趟过的“坑”和解决方案。

1. 环境准备与核心工具栈解析

在动手写代码之前,我们需要先理解并搭建好整个技术栈的基石。这套方案的核心思想是“框架标准化,模型本地化”。

1.1 为什么选择 ADK + Ollama 这个组合?

市面上AI智能体框架不少,比如微软的AutoGen、LangChain等。ADK作为后来者,有几个特点让我觉得它特别适合快速原型开发:

  • “开箱即用”的完整性:ADK不仅仅是一个SDK,它自带Web UI、命令行交互和HTTP API服务。这意味着你写完核心逻辑,立刻就有三种方式与你的智能体交互,省去了自己搭建前后端的麻烦。
  • 对多模型的原生友好:虽然背靠谷歌,但ADK在设计上就是模型无关的。通过集成 LiteLLM 这样的抽象层,它可以无缝对接 OpenAI、Anthropic 乃至任何提供兼容API的模型,当然也包括我们本地运行的Ollama。
  • 清晰的智能体编排范式:ADK对“顺序执行”、“循环”、“并行”等工作流有内置支持。这对于构建一个先理解用户问题、再查询知识库、最后组织回答的客服流程来说,结构会非常清晰。

而 Ollama 则是当前在个人电脑上运行开源大模型最便捷的工具,没有之一。它解决了模型下载、环境配置、服务启动等一系列脏活累活。

我们的技术栈全景图如下:

[你的本地机器]
    ├── Python 3.9+ 环境
    ├── Ollama 服务 (运行 Gemma 2B/7B 等模型)
    └── ADK 应用 (包含你的客服智能体逻辑)
            ├── 通过 LiteLLM 调用本地 Ollama API
            ├── 集成自定义工具(如知识库查询)
            ├── 提供 Web/CLI/HTTP 接口

1.2 逐步安装与验证

我们一步步来,确保每个环节都正确无误。

第一步:安装并验证 Ollama

访问 Ollama 官网下载并安装。安装后,打开终端,拉取一个适合本地运行的轻量级模型。对于客服场景,我们需要模型有较好的指令遵循和对话能力。Google 的 Gemma 系列是一个不错的起点,它在英文和中文上都有尚可的表现,且对硬件要求相对友好。

# 拉取 Gemma 2B 模型(对内存要求最低,适合初次尝试)
ollama pull gemma:2b

# 或者,如果你的机器内存足够(建议8G以上),可以尝试能力更强的 7B 版本
# ollama pull gemma:7b

拉取完成后,启动 Ollama 服务(通常安装后会自动运行)。可以通过以下命令验证:

# 查看已安装的模型
ollama list

# 简单测试模型是否能正常响应
ollama run gemma:2b "Hello, what's your name?"

如果模型能返回自我介绍,说明 Ollama 部分已就绪。

第二步:创建 Python 虚拟环境并安装 ADK

强烈建议使用虚拟环境来管理依赖,避免包冲突。

# 创建并进入一个名为 adk-customer-service 的目录
mkdir adk-customer-service && cd adk-customer-service

# 创建 Python 虚拟环境(这里使用 venv,你也可以用 conda)
python -m venv .venv

# 激活虚拟环境
# 在 macOS/Linux 上:
source .venv/bin/activate
# 在 Windows PowerShell 上:
# .venv\Scripts\Activate.ps1

# 安装 Google ADK 核心库
pip install google-adk

# 安装 LiteLLM,它是 ADK 连接非谷歌模型(如 Ollama)的桥梁
pip install litellm

安装完成后,可以快速验证 ADK 命令行工具是否可用:

adk --help

你应该能看到 run, web, api-server 等子命令的帮助信息。

至此,最基础的环境已经准备好了。接下来,我们将进入核心的智能体创建环节。

2. 创建你的第一个本地模型智能体

现在,让我们用不到20行代码,创建一个能与本地 Gemma 模型对话的智能体。这是验证整个链路是否打通的关键一步。

2.1 项目结构与核心文件

在 adk-customer-service 目录下,创建如下标准的 ADK 项目结构:

adk-customer-service/
├── .env                    # 环境变量配置文件(暂时可为空)
├── customer_service_agent/
│   ├── __init__.py        # 空文件,使文件夹成为Python包
│   └── agent.py           # 智能体的核心代码
└── requirements.txt       # 可选,记录依赖

agent.py 是我们所有魔法的发生地。让我们先创建一个最简单的版本。

2.2 编写基础智能体代码

打开 customer_service_agent/agent.py,输入以下内容:

from google.adk.agents import LlmAgent
from google.adk.models.lite_llm import LiteLlm

# 创建根智能体
root_agent = LlmAgent(
    # 核心:通过 LiteLLM 指定使用本地 Ollama 服务的 Gemma 2B 模型
    # 格式固定为 "ollama/<模型名>"
    model=LiteLlm(model="ollama/gemma:2b"),
    
    name="local_customer_assistant",
    description="一个运行在本地的、基于Gemma模型的客服助手原型。",
    instruction=(
        "你是一个专业的客服助手,负责回答用户关于产品使用、服务咨询和一般性问题。"
        "请保持友好、耐心且专业的语气。"
        "如果你不知道答案,请诚实地告知用户,并建议他们通过其他渠道联系人工客服。"
        "请用中文进行对话。"
    ),
    # 目前 tools 列表为空,智能体仅具备基础对话能力
    tools=[],
)

代码解读:

  1. LlmAgent 是 ADK 中用于构建基于大语言模型智能体的核心类。
  2. model=LiteLlm(model="ollama/gemma:2b") 是连接本地模型的关键。LiteLlm 是一个包装器,它理解 ollama/ 前缀,并会将请求发送到本地的 Ollama API 服务(默认在 http://localhost:11434)。
  3. instruction 中的提示词非常重要,它设定了智能体的“人设”和行为准则。我们明确要求它用中文对话,并扮演客服角色。

2.3 运行与测试智能体

ADK 提供了三种交互方式,我们逐一尝试。

方式一:Web 界面(最直观)

在项目根目录(adk-customer-service)下执行:

adk web

你会看到类似 Starting server at http://localhost:8000 的输出。用浏览器打开这个地址,你将看到一个简洁的聊天界面。在左上角的下拉菜单中,选择我们刚创建的 local_customer_assistant。

现在,尝试输入一些问题,比如:“你们的产品支持哪些操作系统?” 或 “我的账号无法登录,该怎么办?”。观察 Gemma 模型的回复。你可能会立刻发现两个问题:

  1. 回复可能是英文的,尽管我们在 instruction 中要求了中文。
  2. 回复可能比较简短或笼统,缺乏客服场景的细节。

这是我们遇到的第一个“坑”:某些较小的开源模型对中文指令的遵循能力有限,并且缺乏特定领域的知识。别担心,我们会在后续章节解决它。

方式二:命令行交互

在另一个终端窗口(确保虚拟环境已激活),运行:

adk run customer_service_agent

这会进入一个交互式命令行界面,你可以直接与智能体对话。按 Ctrl+C 退出。

方式三:HTTP API 服务

这对于将智能体集成到其他系统(如你的网站后端)非常有用。

adk api-server

服务启动后,你可以使用 curl 或 Postman 等工具向 http://localhost:8000/v1/run 发送 POST 请求来调用智能体。具体的 API 格式可以参考 ADK 文档。

提示:在开发过程中,Web 界面是最方便的,因为它不仅能对话,还能在右侧面板查看详细的执行事件流(Event Stream),包括模型接收的提示词、思考过程和工具调用情况,这对调试至关重要。

3. 增强智能体:工具集成与中文优化

一个只会闲聊的模型还不是合格的客服。我们需要赋予它“超能力”——查询知识库、处理特定任务。同时,也要优化其中文表现。

3.1 为智能体添加自定义工具(函数)

工具(Tools)是智能体与外部世界交互的桥梁。在 ADK 中,任何 Python 函数都可以通过简单的装饰或配置,变成一个智能体可以调用的工具。

假设我们有一个简单的“产品知识库”,实际上可能是一个字典或一个小型数据库。我们创建一个函数来模拟查询过程。

在 agent.py 中,添加以下代码:

# 模拟一个简单的产品知识库
PRODUCT_KB = {
    "subscription_plans": {
        "basic": {"price": "$10/month", "features": ["10GB存储", "基础支持"]},
        "pro": {"price": "$25/month", "features": ["100GB存储", "优先支持", "API访问"]},
        "enterprise": {"price": "定制", "features": ["无限存储",  "专属客户经理", "SLA保障"]}
    },
    "supported_os": ["Windows 10/11", "macOS 10.15+", "主流Linux发行版"],
    "common_issues": {
        "login_failure": "请尝试:1. 检查网络;2. 清除浏览器缓存;3. 重置密码。",
        "upload_error": "通常是由于文件格式不支持或大小超限。请确保文件小于2GB,且为常见格式。"
    }
}

def query_product_knowledge(topic: str, detail: str = None) -> str:
    """
    查询产品知识库。
    
    参数:
        topic: 查询主题,如 'subscription_plans', 'supported_os', 'common_issues'
        detail: 可选,查询具体项,如 'pro' (对于subscription_plans) 或 'login_failure'
    
    返回:
        查询到的信息字符串。
    """
    try:
        if topic not in PRODUCT_KB:
            return f"抱歉,知识库中没有关于 '{topic}' 的主题信息。"
        
        data = PRODUCT_KB[topic]
        
        if detail:
            if isinstance(data, dict) and detail in data:
                result = data[detail]
                if isinstance(result, dict):
                    # 格式化返回,例如套餐详情
                    features = ", ".join(result.get('features', []))
                    return f"{detail} 套餐:价格 {result.get('price', 'N/A')},包含功能:{features}。"
                else:
                    return f"{detail}: {result}"
            else:
                return f"在主题 '{topic}' 下未找到关于 '{detail}' 的详细信息。"
        else:
            # 返回整个主题的摘要
            if isinstance(data, dict):
                return f"关于 {topic},我们有:{', '.join(data.keys())}。请告诉我你想了解的具体项。"
            elif isinstance(data, list):
                return f"支持的操作系统包括:{', '.join(data)}。"
            else:
                return str(data)
    except Exception as e:
        return f"查询知识库时出错:{str(e)}"

现在,我们需要修改 root_agent 的配置,将这个函数添加到 tools 列表中:

root_agent = LlmAgent(
    model=LiteLlm(model="ollama/gemma:2b"),
    name="local_customer_assistant",
    description="一个运行在本地的、基于Gemma模型的客服助手原型,可以查询产品知识库。",
    instruction=(
        "你是一个专业的客服助手,负责回答用户关于产品使用、服务咨询和一般性问题。"
        "请保持友好、耐心且专业的语气。"
        "**重要**:当用户询问价格、套餐、功能、支持的系统或常见问题时,你必须使用 `query_product_knowledge` 工具来获取准确信息,然后基于信息组织回答。"
        "如果你不知道答案,请诚实地告知用户,并建议他们通过其他渠道联系人工客服。"
        "请用中文进行对话。"
    ),
    # 将我们的函数作为工具添加进来
    tools=[query_product_knowledge],
)

重启 adk web 服务,再次与智能体对话。现在尝试问:“你们的Pro套餐多少钱?包含什么功能?” 观察事件流(Event Stream),你应该能看到智能体识别了用户意图,调用了 query_product_knowledge 工具,并基于返回的结果生成了回答。

这个流程的威力在于:你将动态、结构化的数据查询能力赋予了大模型,而模型负责理解自然语言和组织回答。这是构建实用AI应用的核心模式。

3.2 攻克中文与上下文难题

在使用较小的本地模型时,你可能会遇到:

  • “语言漂移”:聊着聊着又变回英文。
  • “上下文健忘”:在多轮对话中忘记之前的约定或信息。
  • “工具调用不稳定”:有时不调用工具直接胡编乱造。

这里有一些经过实践验证的调优技巧:

1. 强化系统提示词(Instruction) 不要只在开头说“请用中文”。在提示词中反复、多角度地强调语言和角色要求。可以尝试这样写:

instruction=(
    "你是一个专业的、只说中文的客服助手。你的所有思考和回复都必须使用简体中文。"
    "你的名字是‘小智’。"
    "你的核心职责是帮助用户解决产品相关问题。"
    "**工具使用规则**:当用户的问题涉及以下关键词时,你必须调用 `query_product_knowledge` 工具:'价格'、'套餐'、'多少钱'、'功能'、'支持'、'系统'、'操作系统'、'问题'、'错误'、'登录'、'上传'。调用工具后,严格依据工具返回的事实信息进行回答,不得编造。"
    "**对话风格**:亲切、简洁、直接。每次回答尽量控制在3句话以内。如果工具返回的信息不足以回答问题,请告知用户并引导他们提供更多细节或联系人工客服。"
    "现在,开始与用户对话吧。"
)

2. 利用会话状态(Session State) ADK 内置了会话管理。虽然对于基础 LlmAgent 是自动的,但了解其原理有助于调试。模型在每次调用时,都会接收到包含整个对话历史的上下文。确保你的 Ollama 模型支持足够长的上下文长度(Gemma 2B 可能只有 4K token)。对于更长的对话,你可能需要考虑启用 ADK 的上下文压缩或总结功能。

3. 考虑升级模型 如果经过大量提示词优化后,Gemma 2B 的表现仍不理想,这是硬件和模型能力本身的限制。可以考虑:

  • 升级到 Gemma 7B(需要更多内存)。
  • 尝试专门为中文优化的模型,如 Qwen2.5-1.5B/7B(同样可通过 Ollama 获取:ollama pull qwen2.5:7b),并在 model 参数中改为 ollama/qwen2.5:7b。

更换模型后,你可能会立刻感受到中文理解、遵循指令和调用工具能力的显著提升。这往往是性价比最高的优化手段。

4. 构建多阶段客服工作流

一个成熟的客服流程往往不是一问一答那么简单。它可能包含:问题分类 -> 信息查询 -> 答案组织 -> 必要时升级或确认。ADK 的 SequentialAgent(顺序智能体)非常适合编排这样的多阶段工作流。

让我们构建一个稍微复杂一点的客服流水线:

from google.adk.agents.sequential_agent import SequentialAgent
from google.adk.agents.llm_agent import LlmAgent
from google.adk.models.lite_llm import LiteLlm

# 共享的模型配置
local_model = LiteLlm(model="ollama/qwen2.5:7b") # 假设我们升级到了Qwen

# **阶段1:问题分类器**
classifier_agent = LlmAgent(
    name="QuestionClassifier",
    model=local_model,
    instruction=(
        "分析用户输入,判断问题类型。只输出一个类型标签,不要任何其他文字。"
        "可选标签:"
        "1. `product_info` - 询问产品功能、价格、套餐等。"
        "2. `technical_support` - 遇到使用错误、无法登录等技术问题。"
        "3. `general_query` - 一般性咨询,如营业时间、联系方式。"
        "4. `complaint` - 用户投诉或不满。"
    ),
    output_key="question_type" # 将输出存储到会话状态中
)

# **阶段2:信息检索员** (根据不同类型使用不同工具或策略)
# 这里我们创建一个统一的检索员,但在实际中可以为不同类型配置不同的子智能体
retriever_agent = LlmAgent(
    name="InfoRetriever",
    model=local_model,
    instruction=(
        "根据会话状态中的 `question_type` 和用户原始问题,调用合适的工具获取信息。"
        "如果类型是 `product_info`,调用 `query_product_knowledge`。"
        "如果类型是 `technical_support`,首先尝试从知识库查询,如果没有,则生成标准排查建议。"
        "其他类型,根据常识和公司政策生成初步回复框架。"
        "输出格式:'检索结果:[这里放获取到的信息或生成的框架]'"
    ),
    tools=[query_product_knowledge], # 可以添加更多工具,如搜索网络、查询工单系统
    output_key="retrieved_info"
)

# **阶段3:回答生成器**
response_agent = LlmAgent(
    name="ResponseGenerator",
    model=local_model,
    instruction=(
        "你是一名专业的客服代表。"
        "请结合以下信息,生成最终给用户的回复:"
        "1. 用户原始问题。"
        "2. 问题类型 (`question_type`)。"
        "3. 检索到的信息 (`retrieved_info`)。"
        "要求:回复必须友好、专业、简洁、有用,并使用中文。如果信息不足,请引导用户提供更多细节或转人工。"
    ),
    output_key="final_response"
)

# **用 SequentialAgent 串联起整个流水线**
customer_service_pipeline = SequentialAgent(
    name="CustomerServicePipeline",
    sub_agents=[classifier_agent, retriever_agent, response_agent]
)

# **最终的根智能体,将流水线作为其子智能体**
root_agent = LlmAgent(
    name="EnhancedCustomerAssistant",
    model=local_model,
    instruction="你是最终的客服助手接口。将用户问题交给内部流水线处理,并将流水线的最终结果返回给用户。",
    sub_agents=[customer_service_pipeline]
)

这个架构的优势在于:

  • 模块化:每个智能体职责单一,易于单独测试和优化。
  • 可控性强:你可以精确控制每个阶段的输入输出,例如,确保分类阶段不会胡编乱造类型。
  • 可观测性:在 ADK 的 Web 事件流中,你可以清晰地看到问题如何流经分类 -> 检索 -> 生成三个阶段,每个阶段的输入输出是什么,极大方便了调试。

要使用这个流水线,你只需像之前一样运行 adk web,然后与 EnhancedCustomerAssistant 对话。你会发现,对于复杂问题,它的处理流程更加结构化,回答的准确性和专业性也更有保障。

5. 部署考量与进阶路线

当你有了一个运行良好的本地原型后,可能会考虑下一步:如何让它更稳定、更强大,甚至部署到生产环境?

5.1 性能与稳定性优化

  • 模型服务化:Ollama 默认运行在开发模式。对于更稳定的服务,可以考虑将模型部署到更专业的推理服务器上,如 vLLM 或 TGI,它们能提供更高的吞吐量、更低的延迟和更好的并发支持。然后让 ADK 通过 LiteLLM 连接到这个新的 API 端点。
  • 记忆与状态持久化:ADK 默认使用内存存储会话。对于需要长期记忆或重启后恢复的场景,你需要实现自定义的 SessionService 和 ArtifactService,将会话数据保存到数据库(如 Redis、PostgreSQL)中。
  • 评估与监控:ADK 内置了评估框架。你可以创建一组测试用例(例如,100个典型的客服问题),定期运行评估,监控智能体回答的质量、工具调用准确率等指标,确保其表现不会随着迭代而下降。

5.2 集成外部能力(MCP协议)

客服系统经常需要连接外部数据源:CRM、订单系统、内部Wiki等。手动为每个系统编写API调用工具很繁琐。这时,Model Context Protocol (MCP) 就派上用场了。

MCP 是一种标准协议,允许各种数据源(如数据库、文件系统、Notion、GitHub)以统一的方式向AI模型暴露“工具”。ADK 原生支持 MCP。这意味着你可以直接使用社区里现成的 MCP 服务器,快速让你的客服智能体获得查询数据库、读取文件等能力,而无需编写大量胶水代码。

例如,你可以运行一个连接公司 PostgreSQL 数据库的 MCP 服务器,然后在 ADK 智能体中配置使用它,你的客服就能直接回答“用户XXX的最新订单状态是什么?”这类具体问题了。

5.3 从本地原型到可部署服务

ADK 应用本质上是一个 Python 应用。你可以很容易地将其容器化:

# Dockerfile 示例
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["adk", "api-server", "--host", "0.0.0.0", "--port", "8000"]

然后使用 Docker Compose 或 Kubernetes 进行部署,同时将 Ollama 或其他模型服务作为另一个容器运行。对于生产环境,谷歌也提供了将 ADK 智能体直接部署到 Google Cloud Vertex AI Agent Engine 的选项,可以获得完全托管的、可扩展的运行环境。

回过头看,我们从一行命令安装 Ollama 开始,到构建出一个具备知识库查询能力、结构化工作流的本地AI客服原型,整个过程的核心在于 ADK 对复杂性的封装 和 Ollama 对本地模型运行的简化。这套组合拳让开发者能快速越过基础设施的障碍,直接聚焦在创造有价值的智能体逻辑上。

本地模型的能力边界正在快速扩展,而像 ADK 这样的框架正在大幅降低智能体应用的开发门槛。今天搭建的这个客服系统,虽然简单,但已经包含了智能体技术的核心要素:模型调用、工具使用、工作流编排。你可以以此为起点,不断融入更强大的模型、更丰富的工具、更复杂的业务逻辑,最终打造出真正能解决实际问题的AI助手。

更多推荐