☰
智能旅行助手Agent实战:用TaoToken统一Key打通前后端分离的多Agent系统
2026/9/28 18:22:53 网站建设 项目流程

1. 为什么旅行规划场景特别适合多 Agent 架构

做智能旅行助手这个项目时,我一开始想的是"一个 Agent 全包了不就行了"。结果第一次跑通就发现,让单个 Agent 同时干景点搜索、天气查询、酒店推荐、行程编排四件事,提示词会膨胀到 800 字以上,而且工具调用经常串味——明明该查天气,它去搜了酒店。

旅行规划天然是一个可分解的任务:景点、天气、酒店、行程编排这四块彼此独立,输入输出边界清晰。前端负责表单交互和结果渲染,后端负责编排多个 Agent 依次执行,这就是典型的前后端分离多 Agent 系统。前端不需要知道后端有几个 Agent,只需要调一个/api/trip/plan接口;后端也不需要关心前端用什么框架,只返回结构化的 JSON。

这套架构落地时最容易被忽略的一环是统一 Key 管理。四个 Agent 都要调 LLM,如果每个 Agent 各自配一套 API Key、各自的 base_url,配置会散落在四五个文件里,换一次 Key 要改半天。这篇就用 TaoToken 的统一 Key 把前后端的配置收敛到两个文件:后端的config.toml和前端的settings.json,然后演示一次多 Agent 串联调用的完整验证。

适合谁看:已经写过单 Agent demo、想往多 Agent 编排走一步的开发者;正在做前后端分离项目、被 Key 管理搞烦的工程师;想跑通一条端到端旅行规划链路的学习者。下面所有配置和命令都可以直接复制。

2. TaoToken 统一 Key 的前置准备

TaoToken 在这里扮演的角色是统一的模型接入层。四个 Agent 不管各自用什么模型,都通过同一个 Key、同一个 base_url 发起请求,后端只需要维护一份配置。这样做的直接好处是:换模型只改一个字段,加 Agent 不用重新配 Key,前端也不需要接触任何密钥。

2.1 拿到统一 Key

先去控制台创建一个 API Key。地址是 https://taotoken.net/console ,登录后在 API Keys 页面点新建,复制生成的sk-开头的字符串。这个 Key 就是后面config.toml和settings.json里要填的东西。

注意:Key 只显示一次,复制后先存到本地密码管理器。不要提交到 Git 仓库,后面会用.gitignore排除配置文件。

2.2 确认接入地址

后端所有 Agent 的请求都打到同一个 base_url:

https://taotoken.net/api

这个地址不加任何 UTM 参数,直接写进配置即可。前端如果也需要直连模型(比如做流式对话预览),同样用这个地址。

2.3 项目结构约定

为了让配置收敛,我们约定后端只读一个config.toml,前端只读一个settings.json。目录大致是这样:

trip-planner/ ├── backend/ │ ├── app/ │ │ ├── agents/ # 四个 Agent 实现 │ │ ├── api/ # FastAPI 路由 │ │ ├── models/ # Pydantic 数据模型 │ │ └── config.py # 读取 config.toml │ ├── config.toml # 后端统一配置(含 TaoToken Key) │ └── requirements.txt ├── frontend/ │ ├── src/ │ │ ├── services/api.ts # 调用后端接口 │ │ └── settings.json # 前端配置(含后端地址) │ └── package.json └── .gitignore

.gitignore里至少加上这两行,避免 Key 泄露:

backend/config.toml frontend/src/settings.json

3. 可复制的 config.toml 与 settings.json 骨架

这一节给出两个配置文件的完整骨架,直接复制改 Key 就能用。

3.1 后端 config.toml

后端用 Python 的tomllib(3.11+ 内置)或tomli读取。四个 Agent 共享同一个[llm]段,不需要各自配置。

# backend/config.toml [llm] # TaoToken 统一接入地址,不加任何参数 base_url = "https://taotoken.net/api" # 从控制台复制的 Key api_key = "sk-你的Key填这里" # 默认模型,四个 Agent 共用 model = "gpt-4o-mini" # 单次请求超时(秒),多 Agent 串联时建议放宽 timeout = 60 # 失败重试次数 max_retries = 2 [agents] # 各 Agent 的模型可以覆盖默认值,不写就用 [llm].model attraction_model = "gpt-4o-mini" weather_model = "gpt-4o-mini" hotel_model = "gpt-4o-mini" planner_model = "gpt-4o" [server] host = "0.0.0.0" port = 8000 # 前端开发服务器地址,用于 CORS cors_origins = ["http://localhost:5173"]

对应的config.py读取逻辑:

# backend/app/config.py import tomllib from pathlib import Path from functools import lru_cache CONFIG_PATH = Path(__file__).resolve().parent.parent / "config.toml" @lru_cache def get_config() -> dict: with open(CONFIG_PATH, "rb") as f: return tomllib.load(f) def get_llm_config() -> dict: return get_config()["llm"]

3.2 前端 settings.json

前端不直接持有模型 Key,只持有后端地址和一些 UI 相关配置。这样即使前端代码被打包分发,也不会泄露密钥。

{ "apiBaseUrl": "http://localhost:8000/api", "requestTimeout": 120000, "pollInterval": 1000, "features": { "enableMap": true, "enableExport": true, "enableEdit": true }, "ui": { "defaultCity": "北京", "defaultDays": 3, "maxDays": 10 } }

前端读取方式(Vite 项目):

// frontend/src/services/config.ts import settings from '../settings.json' export const API_BASE_URL = settings.apiBaseUrl export const REQUEST_TIMEOUT = settings.requestTimeout export const FEATURES = settings.features

3.3 四个 Agent 如何共享同一份 LLM 配置

后端初始化 LLM 客户端时,只读一次config.toml,然后把同一个客户端实例注入到四个 Agent 里。这样四个 Agent 用的是同一个 Key、同一个 base_url,只是model字段可能不同。

# backend/app/agents/llm_client.py from openai import OpenAI from app.config import get_llm_config _llm_config = get_llm_config() client = OpenAI( base_url=_llm_config["base_url"], api_key=_llm_config["api_key"], timeout=_llm_config["timeout"], max_retries=_llm_config["max_retries"], ) def chat(messages: list, model: str | None = None) -> str: resp = client.chat.completions.create( model=model or _llm_config["model"], messages=messages, ) return resp.choices[0].message.content

四个 Agent 各自调用chat(),传入自己的model覆盖值即可。Key 只在config.toml里出现一次。

4. 多 Agent 串联调用的验证请求

配置就绪后,跑一次端到端验证:前端提交表单 → 后端依次调用四个 Agent → 返回结构化行程。

4.1 后端编排入口

# backend/app/agents/orchestrator.py from app.agents.llm_client import chat from app.config import get_config cfg = get_config()["agents"] def run_attraction_agent(city: str, preferences: str) -> str: return chat( [ {"role": "system", "content": "你是景点搜索专家,只返回景点名称和一句话描述。"}, {"role": "user", "content": f"搜索{city}符合{preferences}的3个景点。"}, ], model=cfg["attraction_model"], ) def run_weather_agent(city: str, days: int) -> str: return chat( [ {"role": "system", "content": "你是天气查询专家,返回未来几天的天气,温度用纯数字。"}, {"role": "user", "content": f"查询{city}未来{days}天的天气。"}, ], model=cfg["weather_model"], ) def run_hotel_agent(city: str, accommodation: str) -> str: return chat( [ {"role": "system", "content": "你是酒店推荐专家,返回酒店名称和价格区间。"}, {"role": "user", "content": f"推荐{city}的{accommodation}。"}, ], model=cfg["hotel_model"], ) def run_planner_agent(city, days, attraction_out, weather_out, hotel_out) -> str: prompt = f"""根据以下信息生成{city}的{days}日行程,返回 JSON: 景点:{attraction_out} 天气:{weather_out} 酒店:{hotel_out} JSON 字段:city, days[{date, attractions[], meals[], hotel}], weather_info[], budget{{total}}""" return chat( [ {"role": "system", "content": "你是行程规划专家,只返回合法 JSON。"}, {"role": "user", "content": prompt}, ], model=cfg["planner_model"], ) def plan_trip(city: str, days: int, preferences: str, accommodation: str) -> dict: attraction_out = run_attraction_agent(city, preferences) weather_out = run_weather_agent(city, days) hotel_out = run_hotel_agent(city, accommodation) planner_out = run_planner_agent(city, days, attraction_out, weather_out, hotel_out) return {"raw": planner_out}

4.2 FastAPI 路由

# backend/app/api/trip.py from fastapi import APIRouter from pydantic import BaseModel from app.agents.orchestrator import plan_trip router = APIRouter() class TripRequest(BaseModel): city: str days: int preferences: str = "历史文化" accommodation: str = "经济型酒店" @router.post("/trip/plan") async def create_plan(req: TripRequest): return plan_trip(req.city, req.days, req.preferences, req.accommodation)

4.3 用 curl 验证一次串联调用

启动后端:

cd backend pip install fastapi uvicorn openai tomli uvicorn app.api.trip:router --reload --port 8000

发一次请求:

curl -X POST http://localhost:8000/trip/plan \ -H "Content-Type: application/json" \ -d '{"city":"北京","days":3,"preferences":"历史文化","accommodation":"经济型酒店"}'

预期返回里能看到raw字段包含一段 JSON 字符串,里面有city、days、weather_info、budget这些字段。如果四个 Agent 都正常返回,说明统一 Key 配置生效了。

4.4 前端调用

// frontend/src/services/api.ts import axios from 'axios' import { API_BASE_URL, REQUEST_TIMEOUT } from './config' const api = axios.create({ baseURL: API_BASE_URL, timeout: REQUEST_TIMEOUT, }) export async function generateTripPlan(payload: { city: string days: number preferences: string accommodation: string }) { const { data } = await api.post('/trip/plan', payload) return data }

前端表单提交后调用generateTripPlan,拿到结果渲染到 Result 页面。整个链路里前端只碰后端地址,不碰模型 Key。

5. 本篇常见错排查

5.1 401 Unauthorized

最常见的原因是config.toml里的api_key没填对,或者复制时带了空格。检查方法:

grep api_key backend/config.toml

确认是sk-开头、没有多余引号。如果 Key 是从控制台复制的,注意不要漏掉末尾字符。

5.2 连接超时 / Connection refused

先确认base_url写的是https://taotoken.net/api,没有多余路径。然后单独测一下连通性:

curl -X POST https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'

如果这条命令能返回,说明 Key 和地址都没问题,问题出在代码里。如果这条也失败,检查网络和 Key 状态。

5.3 多 Agent 串联时某个 Agent 返回空

四个 Agent 是串行执行的,前一个的输出作为后一个的输入。如果景点 Agent 返回空字符串,天气 Agent 还能跑,但规划 Agent 拿到的输入就不完整。排查方法是在orchestrator.py里每个 Agent 调用后加一行日志:

import logging logger = logging.getLogger(__name__) def run_attraction_agent(city, preferences): out = chat([...], model=cfg["attraction_model"]) logger.info("attraction_agent output: %s", out[:200]) return out

看日志里哪个 Agent 的输出是空的,再针对性检查它的提示词和模型配置。

5.4 前端 CORS 报错

后端config.toml里的cors_origins要包含前端实际地址。如果前端跑在http://localhost:5173,就写这个;如果换了端口,同步改。FastAPI 里这样挂:

from fastapi.middleware.cors import CORSMiddleware from app.config import get_config app.add_middleware( CORSMiddleware, allow_origins=get_config()["server"]["cors_origins"], allow_methods=["*"], allow_headers=["*"], )

5.5 规划 Agent 返回的不是合法 JSON

规划 Agent 的提示词里要明确"只返回合法 JSON,不要加解释"。如果模型还是加了 markdown 代码块,可以在解析前做一次清洗:

import json, re def parse_planner_output(raw: str) -> dict: cleaned = re.sub(r"^```json\s*|\s*```$", "", raw.strip()) return json.loads(cleaned)

如果清洗后还是解析失败,把planner_model换成更强的模型(比如gpt-4o),弱模型在长 JSON 输出上容易出错。

6. 下一步:把配置和验证动作固化下来

到这里,一条端到端链路已经跑通了:前端表单 → 后端/trip/plan→ 四个 Agent 串联 → 返回结构化行程。统一 Key 的价值在于,后面不管加多少个 Agent,config.toml里的[llm]段都不用动,只需要在[agents]里加一行模型覆盖。

如果你要接着往下做,建议先把两个配置文件固化到项目模板里,再写一个make verify脚本,把 4.3 节那条 curl 命令包进去,每次改完 Agent 逻辑跑一次,确认四个 Agent 都正常返回。这样多 Agent 系统的回归成本会低很多。

需要查 Key 用量或新建 Key,去控制台:https://taotoken.net/console 。接入文档在 https://taotoken.net/doc ,里面有各语言 SDK 的 base_url 配置示例。如果后面要把这套多 Agent 编排用到长期编码或 Agent 工作流里,可以看 Coding Plan:https://taotoken.net/coding-plan 。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询