做AI Agent开发,最容易被低估的就是基础设施。看起来好像用LangChain搭个Agent,把工具一注册,项目就能跑,但真到了要做“问数项目”这种必须对接真实数据源、执行真实SQL查询的业务型Agent时,前期底座搭得牢不牢,直接决定你是每天偷着乐还是半夜起来撕代码。在LCODER这个系列里,我们正在从零搭一个问数项目智能体,上一篇拆了整体思路,这篇直接进入基础设施搭建,讲清楚这个“地基”到底怎么打、为什么这么打。
如果你也准备做类似的事——让用户用自然语言提问,Agent自己完成查表、写SQL、出结果——那这篇内容应该能帮你省掉不少弯路。下面我会把基础设施的每一个模块,包括环境选型、模型网关、会话层、数据接入、SQL安全、缓存、向量库、可观测性、评估集,全部过一遍,并且给出我实测下来的具体方案和坑位提示。
1. 问数Agent的基础设施包含哪些部分
很多同学一上来就急着写Agent逻辑,结果做到一半发现连数据源管理都没做,每个工具函数里各写各的数据库连接,模型Key散落在不同文件里,换个模型要改十几处代码。这不是开发Agent,这是给未来埋雷。
1.1 从一条查询请求反推“地基清单”
我先带你从一条真实请求出发,看看系统到底需要哪些基础能力。
假设用户问:“上个月华东区各品类销量排名前五的产品有哪些”。这条请求从进入系统到返回答案,大致要经历以下环节:
- 接收请求:需要一个API服务,负责鉴权、限流、参数校验;
- 识别意图:Agent判断这是数据查询类问题,且涉及“华东区”“上个月”“销量排名”等业务词;
- 检索知识:从元数据中找到“华东区”对应的地区字段、从哪张表统计销量、“上个月”如何映射到时间条件;
- 生成SQL:LLM根据业务字段和表结构生成一条SELECT语句;
- 执行SQL:连接数据库执行查询,拿到结果集;
- 组织回答:把数字结果整理成可读的文本或图表数据。
这个过程看起来很短,但背后需要的东西不少:模型网关、会话管理、元数据同步、向量检索、SQL安全校验、缓存策略、日志追踪、评测回归。这些不是“锦上添花”,而是问数Agent能不能稳定工作的底座。
1.2 先定原则:为什么基础设施决定Agent成败
我见过太多Agent Demo,跑通一次就发朋友圈,但实际上线之后全是问题。问数项目尤其如此,它不像聊天机器人那样容忍模糊,数据的准确性、查询的安全性、响应的时间上限都是硬指标。
所以我在设计基础设施时,给自己定了三个原则:
- 统一入口,禁止散装调用。所有模型调用、数据库连接、外部工具,都必须走统一封装后的服务,不能各个工具函数自己new一个client。这条原则能救命,后面接第二个数据源、切换模型时你就懂了。
- 凡是外部依赖,都要有降级和超时。数据库卡死、模型超时、向量库不可用,这类情况在真实环境一定会遇到。基础设施层必须提前留好兜底方案。
- 从第一天就把可观测性和评估做进去。Agent的推理链路比传统接口长很多,不提前埋点,出了问题根本没法定位是哪一步错了。
这三条原则听起来朴素,但真能一直坚持下来,项目后期会非常省心。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境选型与工程初始化
基础环境这块没什么高深内容,但选型不对后面会很拧巴。我按实际踩坑经验,把推荐方案和理由写清楚。
2.1 开发机、运行时与依赖管理
问数Agent的日常开发主要是写Python服务、调试LangGraph流程、连各种数据源做验证,资源消耗不算夸张。
- 开发机配置:16GB内存起步,32GB会更舒服。主要是IDE、Docker、本地向量库、偶尔跑个小模型同时开的时候,内存很容易吃紧。
- Python版本:3.10或3.11都行,不建议用3.12以下太旧的环境。第三方库对新版本兼容性已经很好,但个别依赖锁版本时会有坑。
- 包管理器:我建议用
uv,创建虚拟环境和安装依赖都极快,比pip+venv省太多时间。如果你还是习惯poetry,也可以,但要锁定版本。 - 核心依赖:FastAPI、LangChain/LangGraph、SQLAlchemy 2.x、OpenAI SDK(或任意兼容OpenAI协议的SDK)、Qdrant客户端、Redis客户端、pydantic-settings、structlog。
有个非常关键的提醒:LangChain系列库的版本兼容性非常“娇气”,升级小版本都可能引发依赖冲突,尤其是pydantic版本。锁版本是必须做的事。我的项目里直接用了具体的版本号,而不是^或者>=,避免哪天别人拉完代码跑不起来。
2.2 目录结构设计:按“层”而不是按“页面”划分
问数Agent工程涉及的东西杂,我建议一开始就把目录按职责分层,而不是按功能散放。下面是我实际用的目录结构:
code复制lcondor_qa_agent/
├── app/
│ ├── api/ # HTTP接口层:FastAPI路由、请求模型
│ ├── agent/ # Agent编排层:LangGraph流程、工具、提示词
│ │ ├── graph.py
│ │ ├── tools/
│ │ └── prompts/
│ ├── core/ # 核心基础能力:模型网关、配置、安全
│ │ ├── llm_gateway.py
│ │ ├── session.py
│ │ ├── security.py
│ │ └── config.py
│ ├── data/ # 数据接入层:数据源注册、SQL执行器、元数据同步
│ │ ├── sources.py
│ │ ├── executor.py
│ │ └── metadata.py
│ ├── memory/ # 记忆与缓存:Redis客户端、向量库
│ │ ├── cache.py
│ │ └── vector_store.py
│ └── observability/ # 日志、追踪、评估埋点
│ ├── logger.py
│ └── tracing.py
├── eval/
│ ├── datasets/
│ └── runner.py
├── scripts/
└── pyproject.toml
一眼看过去,核心就分成api、agent、core、data、memory、observability这几大块。我的体会是“core”和“data”一定要分清楚,模型网关放在core,数据源管理放在data,Agent编排放在agent,这样多人协作时互不干扰。
2.3 全局配置与密钥管理的落地方式
配置管理是我刚开始不重视、后来吃了大亏的地方。问数项目涉及模型Key、多个数据库DSN、Redis地址、向量库地址,这些环境相关的东西如果写死在代码里,换环境就是灾难。
我直接用pydantic-settings统一管理,一个settings.py搞定:
python复制class Settings(BaseSettings):
app_name: str = "lcondor-qa-agent"
env: str = "dev"
llm_api_key: str
llm_base_url: str = "https://api.openai.com/v1"
llm_default_model: str = "gpt-4o-mini"
llm_fallback_model: str = "gpt-4o-mini"
redis_url: str = "redis://localhost:6379/0"
qdrant_url: str = "http://localhost:6333"
sql_timeout: int = 10
sql_max_rows: int = 500
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
配置文件统一走.env,不同环境用不同文件。每次部署时只要改环境变量,不需要动代码逻辑。密钥严禁提交到Git,这一点值得反复强调。
3. 模型网关与Agent会话层搭建
我认为这是问数Agent基础设施里最核心的一层,如果只能先做两件事,那就先做模型网关和会话管理。
3.1 模型网关:为什么要做一层统一抽象
模型网关就是所有模型调用的统一入口。之所以要这层,不只是为了减少重复代码,更是为了应对“模型替换”和“异常降级”这两个真实场景。
问数项目里,SQL生成模型和最终回答模型可以分离使用,线上主力模型和备用模型要能随时切换,不同环境用不同厂商的模型也要能无缝衔接。如果代码里到处直接调用模型SDK,这些全都做不到。
我封了一个非常简洁的网关:
python复制# app/core/llm_gateway.py
import asyncio
import logging
from openai import AsyncOpenAI, OpenAIError
logger = logging.getLogger(__name__)
class LLMGateway:
def __init__(self, settings):
self._client = AsyncOpenAI(
api_key=settings.llm_api_key,
base_url=settings.llm_base_url,
)
self._default_model = settings.llm_default_model
self._fallback_model = settings.llm_fallback_model
async def chat(
self,
messages: list[dict],
model: str | None = None,
temperature: float = 0.1,
max_tokens: int = 4096,
timeout: float = 30.0,
use_fallback: bool = True,
) -> str:
target_model = model or self._default_model
try:
resp = await asyncio.wait_for(
self._client.chat.completions.create(
model=target_model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
),
timeout=timeout,
)
return resp.choices[0].message.content
except OpenAIError as e:
logger.warning("primary model failed, model=%s, error=%s", target_model, e)
if use_fallback:
resp = await self._client.chat.completions.create(
model=self._fallback_model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
)
return resp.choices[0].message.content
raise
这里有几个细节值得多说一句:
- 为什么统一走OpenAI兼容协议?因为不管是OpenAI、Anthropic、还是本地部署的Qwen等模型,很多都提供OpenAI兼容接口,统一协议之后,换模型就是改base_url和model名的事,代码不需要动。
- temperature为什么默认给到0.1?问数场景需要生成准确SQL,温度越低输出越稳定。那些花里胡哨的“创意”在数据分析场景里就是灾难。后面的总结回答可以放宽到0.3,但SQL生成阶段我建议一律0.0到0.2。
- fallback机制必须有,但日志必须打清楚。什么时候降级、降级到哪个模型、因为什么错误,这些都要记下来,否则线上出现问题会非常难查。
3.2 会话管理与上下文组织
问数Agent不是一次性的问答,用户往往会在一个会话里连续追问,比如先问“华东区销量TOP5”,再问“换成华南区呢”。这就要求Agent记住上一轮的条件。
但会话记忆不是简单把历史消息全塞给LLM。问数场景上下文很长,全塞会爆token,还会让模型分不清主次。我的做法是三层结构:
- 短期上下文:最近3轮对话消息,转成轻量摘要存入Redis;
- 核心状态:抽取出每轮确定的过滤条件、涉及的表、默认维度,存成结构化state;
- 长期参考:用户的常用数据范围、常用业务口径,单独存一份用户偏好配置。
会话层我用一个简单的SessionManager:
python复制# app/core/session.py
import json
import uuid
import redis.asyncio as aioredis
class SessionManager:
def __init__(self, redis_client):
self._redis = redis_client
self.ttl = 60 * 60 * 2 # 2小时
async def create_session(self, user_id: str) -> str:
session_id = str(uuid.uuid4())
await self._redis.setex(
f"session:{session_id}",
self.ttl,
json.dumps({"user_id": user_id, "state": {}, "history": []}),
)
return session_id
async def get_session(self, session_id: str) -> dict | None:
raw = await self._redis.get(f"session:{session_id}")
return json.loads(raw) if raw else None
这里有个容易被忽视的点:会话TTL不能太长,问数项目涉及业务数据,长时间不用的会话直接过期更安全。2小时是个折中值,用户短时间连续追问不会断,隔天再聊又是全新会话。
上下文组织顺序我建议固定为:system prompt -> 全局业务口径(浓缩版) -> 检索到的相关表结构 -> 最近3轮关键历史 -> 当前问题。这个顺序我实测下来比打乱顺序准确率高不少,模型对“当前问题位于末尾”的感知更强。
3.3 Agent编排框架落地方案
编排层我选的是LangGraph,因为问数流程天然适合有向图表达。
python复制# app/agent/graph.py
from typing import TypedDict
from langgraph.graph import StateGraph, END
class AgentState(TypedDict):
query: str
schemas: list
sql: str
records: list
answer: str
error: str | None
def build_agent_graph():
g = StateGraph(AgentState)
g.add_node("parse_query", parse_query)
g.add_node("retrieve_schema", retrieve_schema)
g.add_node("generate_sql", generate_sql)
g.add_node("validate_sql", validate_sql)
g.add_node("execute_sql", execute_sql)
g.add_node("generate_answer", generate_answer)
g.set_entry_point("parse_query")
g.add_edge("parse_query", "retrieve_schema")
g.add_edge("retrieve_schema", "generate_sql")
g.add_edge("generate_sql", "validate_sql")
g.add_conditional_edges("validate_sql", decide_after_validation, {
"retry": "generate_sql",
"pass": "execute_sql",
"fail": END,
})
g.add_edge("execute_sql", "generate_answer")
g.add_edge("generate_answer", END)
return g.compile()
现在只需要把每个节点的函数实现好就行。这步先把骨架立住,后面每一步都可以单独优化,不至于推倒重来。
4. 数据接入与SQL执行:问数项目的“心脏”
问数Agent和普通聊天Agent最大的区别,就是它要连真实数据库、跑真实SQL。这条链路如果做不扎实,一切上层智能都是空中楼阁。
4.1 数据源注册与管理
数据源不能写成死配置,最好做成可注册的统一对象。一个数据源至少包含:名称、类型、只读连接串、同步策略、所属业务域。
yaml复制# config/data_sources.yaml
data_sources:
sales_mysql:
type: mysql
readonly_dsn: mysql+pymysql://qa_ro:password@10.0.0.5:3306/sales
sync_cron: "*/10 * * * *"
business_domain: sales
analytics_clickhouse:
type: clickhouse
readonly_dsn: clickhouse://qa_ro:password@10.0.0.6:8123/analytics
sync_cron: "*/5 * * * *"
business_domain: analytics
我只允许Agent连接只读账号,这是基本原则,后面说安全时还会再展开。
连接管理用SQLAlchemy 2.x非常方便,为每个数据源动态创建Engine并放入全局注册表:
python复制# app/data/sources.py
from sqlalchemy import create_engine
class DataSourceRegistry:
def __init__(self):
self._engines = {}
def register(self, name: str, dsn: str):
engine = create_engine(
dsn,
pool_size=5,
max_overflow=10,
pool_pre_ping=True,
)
self._engines[name] = engine
def get(self, name: str):
return self._engines[name]
注意pool_pre_ping=True,这个参数会避免连接池里的连接长时间不用被数据库断开后,Agent执行SQL时报“Lost connection”的错误。问数Agent经常一阵子没有查询请求,这个细节能避免很多莫名其妙的问题。
4.2 元数据同步与表结构解析
问数Agent要生成正确SQL,前提是它知道这个库里有哪些表、表有哪些字段、字段的业务含义是什么。这套信息我统一称为元数据。
常见做法是定时任务从information_schema拉取表结构,然后做两件事:一是保存一份结构化JSON作为缓存,二是写入向量库供检索使用。
python复制# app/data/metadata.py
from sqlalchemy import text
def fetch_table_metadata(engine, schema: str = "public"):
queries = []
sql = text("""
SELECT table_name, column_name, data_type, column_comment
FROM information_schema.columns
WHERE table_schema = :schema
""")
rows = engine.execute(sql, {"schema": schema}).mappings().all()
for row in rows:
queries.append({
"table": row["table_name"],
"field": row["column_name"],
"type": row["data_type"],
"comment": row["column_comment"],
})
return queries
字段注释真的非常重要。很多问数Agent生成SQL不准,根本原因是底层数据库字段没注释,或者注释是乱写的。我在项目启动时就会推动业务方把字段注释补全,同时人工整理一份业务数据字典,比如“销售额=订单金额-退款金额”“活跃用户=登录次数大于0的用户”这类口径。
如果数据字段本身没有注释,基础设施里就要提供“人工补录”的入口。你不可能指望LLM在没有注释的情况下自己理解“col_a”“col_b”是什么意思,这是反人性的。
4.3 SQL执行安全、限流与超时
问数Agent有一个特殊风险:它生成的SQL可能因为理解偏差而变成危险语句,或者执行一个超重的查询把线上库拖垮。这个话题在做数据基础设施时必须严肃对待。
我总结了三层防护,每一层都不能省:
第一层,连接层强制只读账号。Agent使用的数据库账号在MySQL里只给SELECT权限,在PostgreSQL里只配置只读角色。这样就算LLM抽风生成了DELETE语句,数据库层面也会直接拒绝。但注意有些DDL语句对特定权限账号依然可能执行,所以第二层不能省。
第二层,代码层关键词拦截。所有SQL提交执行前,做一次词法检查:
python复制# app/core/security.py
FORBIDDEN_KEYWORDS = ["insert", "update", "delete", "drop", "create", "alter", "truncate", "grant", "rename", "call"]
def validate_sql(sql: str) -> bool:
sql_stripped = sql.strip().lower()
if sql_stripped.count(";") > 1:
return False
first_keyword = sql_stripped.split(" ", 1)[0]
if first_keyword != "select":
return False
for word in FORBIDDEN_KEYWORDS:
if word in sql_stripped:
return False
return True
第三层,执行层加超时和行数限制。数据库连接超时、查询执行超时、返回行数限制,这三个参数必须由后端统一控制。命令行客户端用SET statement_timeout,MySQL执行前加SET MAX_EXECUTION_TIME,同时应用层用asyncio.wait_for做兜底。
python复制async def execute_sql(engine, sql: str, timeout: int = 10):
async with engine.connect() as conn:
result = await asyncio.wait_for(
conn.execute(text(sql + " LIMIT 500")),
timeout=timeout,
)
rows = result.fetchall()
return rows
这里还有个细节:问数项目的查询结果不需要全量返回,500行足够给LLM做总结分析了。如果用户想看全量,应该引导他去BI工具,而不是在Agent里跑一个几百万行的结果集。
5. 记忆、缓存与向量检索基础设施
问数Agent很多回答看起来“笨”,不是模型不行,而是缺少业务知识和历史经验的沉淀。这部分就靠缓存和向量库来补。
5.1 Redis缓存分层设计
我在问数项目里用Redis做了三层缓存,每一层的TTL和key设计都不一样:
- 会话缓存:存会话状态和最近对话摘要,TTL 2小时,key为
session:{session_id}; - 查询结果缓存:相同问题、相同参数在短时间内的查询结果直接复用,TTL 10分钟,key为
qa:{scene}:{user}:{query_sig}:{param_hash}; - 元数据缓存:表结构字典、数据口径、同义词这类几乎不变的信息,TTL 30分钟以上,key为
schema:{data_source}。
为什么查询结果缓存TTL只设10分钟?因为业务数据一直在变,缓存太久用户会发现数字对不上。10分钟既能挡住瞬时重复请求,又不会让结果过时太严重。
一个容易踩的坑是query_sig的生成。别直接用整条用户问题做key,用户可能只是改了标点,或者换了一种说法,缓存就可能失效。我一般会把问题做归一化处理——转小写、去标点、统一全半角——再做hash。
5.2 向量库初始化与业务知识沉淀
为什么要向量库?因为在问数项目里,LLM生成SQL前需要知道“用户问的业务词对应哪张表的哪个字段”。比如用户说“华东区”,它要能关联到地区的字段;“销量排名前五”要能关联到order表或者sales表。
这种映射关系最适合向量检索。我把三类信息写进向量库:
- 表结构向量:每张表、每个字段生成一条embedding,meta里存表名、字段名、字段注释、所属数据源;
- 业务口径向量:人工整理的业务术语、指标定义、同义词,比如“华东 = 上海+江苏+浙江+安徽+福建+江西”;
- 历史问答对向量:用户之前问过的问题以及最终生成的SQL和结果,存下来作为示例参考。
我用的向量库是Qdrant,部署非常简单:
bash复制docker run -d --name qdrant -p 6333:6333 qdrant/qdrant:latest
初始化collection时,我会建三个collection,分别对应上面三类信息。写入时用batch upsert,每批100条,避免一次性写入太多导致超时。
检索时我一般取top 20条候选,再做相似度阈值过滤,低于0.5的不要,宁可漏掉也不要把噪声塞给LLM。
embedding模型我推荐bge-m3或者OpenAI的text-embedding-3-small。如果在本地环境跑,bge-m3更可控,不用把表结构数据发到外部。这里没有标准答案,看你的数据敏感程度选。
向量库初始化是基础设施里最容易被忽略的一块。很多人急着写SQL生成逻辑,结果LLM每次都在“盲猜”表结构,生成的SQL五花八门。把表结构、业务口径先喂进向量库,再让LLM根据检索结果生成SQL,准确率能提升一大截。
6. 可观测层与评估集搭建
Agent项目Debug难度比普通接口高很多,因为一个请求要经过“意图解析 -> schema检索 -> SQL生成 -> SQL执行 -> 回答生成”多跳链路,中间任何一跳出错都会导致最终结果不对。没有可观测性,出了问题你连从哪查起都不知道。
6.1 全链路追踪与结构化日志
我会给每一次Agent请求生成一个trace_id,从HTTP入口开始,贯穿到每一个节点和日志。所有日志都使用结构化输出,方便后续检索。
python复制# app/observability/logger.py
import structlog
def setup_logging():
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
]
)
关键埋点数据建议记录这些字段:
- request_id / trace_id:链路追踪ID;
- user_id:提问用户;
- scene:场景标识;
- model:实际使用的模型;
- prompt_tokens / completion_tokens:token用量;
- agent_steps:走了哪些节点,每个节点耗了多少毫秒;
- sql / sql_cost_ms:最终执行的SQL以及耗时;
- error:如果出错,错误信息是什么。
如果你预算允许,可以上个Langfuse或者LangSmith做可视化追踪。但即使不上这些平台,自研这套埋点也能让你在排查问题时“有据可查”。问数项目上线那天,你会感激当初多写的这些日志。
6.2 离线评估集与回归机制
问数Agent有个大坑:今天调prompt改好了一个问题,过了几天发现另一个问题变差了。这个问题的根源就是没有评估集做回归。
我建议从项目第一天就开始积累评估集。格式很简单,就是一个CSV或者JSON文件:
json复制[
{
"question": "上个月华东区各品类销量排名前五的产品有哪些",
"data_source": "sales_mysql",
"expected_sql_keyword": ["sales", "region", "limit", "last_month"],
"expected_answer_range": "返回5条记录,包含产品名称和销量"
}
]
评估分两个维度:
- SQL生成准确性:对比生成SQL里是否包含关键表名、关键过滤条件、limit等要素,这个可以用规则自动判断;
- 最终回答质量:用LLM-as-a-judge打分,固定一个评测prompt,要求按正确性、完整性、可读性打分,1到5分。
我每周会跑一次回归,把评估集里所有问题都过一遍,统计通过率和平均分。如果分数比上周低,立刻排查是改了什么代码或配置导致的。没有这套机制,问数Agent上线后就是“薛定谔的准确率”,你不知道它什么时候会退化。
7. 常见问题与排查技巧实录
最后把我在搭建过程中实际遇到的高频问题整理成一个速查表,每个问题都付过学费。
| 现象 | 根本原因 | 排查与解决 |
|---|---|---|
启动报pydantic相关错误 |
LangChain生态和pydantic v2版本冲突 | 锁定LangChain相关包的精确版本,不要用^范围依赖 |
| 向量库搜索后LLM还是“乱猜表名” | 没有做业务同义词映射,比如“销量”匹配不到sal_amt字段 |
建business_glossary集合,把业务词与字段名的对应关系写进去 |
| 执行SQL时一直卡住,接口超时 | 忘了在连接层和应用层同时设超时 | 客户端连接串加connect_timeout,查询语句用asyncio.wait_for包一层 |
| 上下文token超限 | 把全量表结构一次性塞进了prompt | 改为检索式注入,先把表结构向量化,每次只注入相关表的字段信息 |
| 换了本地模型后准确性暴跌 | 本地模型对复杂指令遵循能力较差 | 降低检索阈值,只喂最相关的3-5张表,把prompt写成更明确的步骤式指令 |
| 多轮对话中Agent突然忘了上一轮的条件 | 会话里只存了消息原文,没存结构化state | 每轮解析后把确定的条件和参数写入session state,下一轮优先参考state |
还有两个心得必须分享:
第一,问数Agent的表结构信息宁少勿多。别把全库30张表都塞给LLM。我试过在prompt里放全量schema,结果模型经常从无关表里拼出错误SQL,准确率反而下降了。正确的做法是向量检索只返回和当前问题强相关的3-5张表,相关信息控制在几百个token以内,模型反而更专注。
第二,数据源抽象层一定要在一开始就做好。我们当时图省事,先只接了一个MySQL,后来要接ClickHouse时,几乎把工具层和SQL执行层重构了一遍。如果你预计后面会接多个数据源,基础设施阶段就按多数据源来设计,哪怕第一个月只用一个源,也值得按多源架构写。
根据我个人的经验,问数Agent基础设施搭建最忌讳“赶”。你可能会觉得这些配置、封装、埋点很繁琐,想先跑通一个Demo再说。但我可以负责任地讲,Demo只需要半天就能跑通,真正折磨人的是后面每次接新数据源、换新模型、排查线上问题时的返工。花两天时间把底座打好,比上线后用一个月的加班来还债,划算太多。
这个系列后面还会继续深入Agent的具体实现细节,比如SQL生成节点怎么调prompt、执行出错后如何自动修复、多数据源路由怎么设计。基础设施这部分先落地,后面迭代就有了稳固的依托。
