先把问题摸透:4个核心瓶颈
优化前我先把整个流程拆开来测,发现问题就出在这四点:
- 向量检索(RAG)每次都重新查,光这一步就耗3-5秒,还重复执行,纯纯浪费资源;
- 外部数据文件每次调用都重新解析,文件不大但解析一次也要秒级,完全没必要;
- 工具调用链太碎,拿个用户ID和月份都要分两次调用,多一次调用就多一次网络和序列化开销;
- 依赖外部API,网络一波动就超时失败,测试时错误率都到7%了。
我的目标很明确:先把重复操作干掉,再简化流程,最后把稳定性提上来,不求一步到位,但要先解决高收益的问题。
第一步:给RAG检索加缓存,从3秒到0.1秒
向量检索是耗时大头,而且同一个查询会反复出现,最适合加缓存。我没搞复杂的分布式缓存,先从内存缓存入手,简单直接见效快。
核心思路就是:第一次查完把结果存起来,30分钟内再查同一个问题,直接拿缓存里的,还得定期清过期的,避免内存爆了。
python
# 先初始化缓存和过期时间
rag_cache = {}
CACHE_EXPIRY_TIME = 30 * 60 # 30分钟过期
def get_cache_key(query: str) -> str:
# 转小写去空格,避免大小写、空格导致缓存失效
return f"rag_{query.lower().strip()}"
@tool(description="从向量存储中检索参考资料")
def rag_summarize(query: str) -> str:
import time
cache_key = get_cache_key(query)
# 缓存命中直接返回
if cache_key in rag_cache and time.time() - rag_cache[cache_key]['timestamp'] < CACHE_EXPIRY_TIME:
logger.info(f"[缓存命中] 直接用{query}的检索结果")
return rag_cache[cache_key]['result']
# 没命中就执行检索,记个耗时
start_time = time.time()
result = rag.rag_summarize(query)
logger.info(f"[检索完成] 这次查了{time.time()-start_time:.2f}秒")
# 把结果存进缓存
rag_cache[cache_key] = {
'result': result,
'timestamp': time.time()
}
# 顺手清过期缓存
_clean_expired_cache()
return result
def _clean_expired_cache():
"""定期清过期缓存,避免内存泄漏"""
import time
expired_keys = [k for k, v in rag_cache.items() if time.time() - v['timestamp'] >= CACHE_EXPIRY_TIME]
for key in expired_keys:
del rag_cache[key]
这么改完,只要是30分钟内查过的问题,响应时间直接从3-5秒降到0.1秒以内,重复检索的负载也少了一大半。我还加了日志,能看到缓存命中率,方便后续调整过期时间。
第二步:外部数据只加载一次,秒级变毫秒级
之前每次拿用户月度数据,都会重新读文件、解析,哪怕数据根本没变化。我就加了个全局标记,第一次调用时加载完存内存,之后直接用:
python
_external_data_loaded = False # 标记数据是否已加载
def generate_external_data():
global _external_data_loaded
if not _external_data_loaded:
start_time = time.time()
external_data_path = get_abs_path(agent_conf["external_data_path"])
with open(external_data_path, "r", encoding="utf-8") as f:
# 解析数据的逻辑...
_external_data_loaded = True
logger.info(f"外部数据加载完,耗时{time.time()-start_time:.2f}秒")
@tool(description="获取用户月度使用记录")
def fetch_external_data(user_id: str, month: str) -> str:
generate_external_data() # 确保数据已加载
try:
return external_data[user_id][month]
except KeyError:
logger.warning(f"没找到{user_id} {month}的记录")
return ""
就加了一个布尔标记,数据加载从每次秒级变成只有第一次加载耗几秒,后续都是毫秒级访问,这个改动成本最低,收益却最明显。
第三步:合并工具调用,少走弯路
之前拿用户ID和月份要调两个工具,我直接合了一个get_user_info,一次返回JSON格式的用户ID+月份:
python
@tool(description="获取用户ID和当前月份")
def get_user_info() -> str:
import json
user_info = {
"user_id": random.choice(user_ids),
"month": random.choice(month_arr)
}
return json.dumps(user_info)
工具调用链从"拿ID→拿月份→拿数据→生成报告"简化成"拿用户信息→拿数据→生成报告",调用次数少了30%左右,少一次调用就少一次网络往返和数据处理,积少成多,稳定性也跟着提了。
最后看看效果:没花大成本,却解决了大问题
优化完测了几组用例,虽然平均耗时从42.3秒降到38.92秒(个别用例因为网络波动略涨),但核心环节的提升是实打实的:
- 向量检索缓存命中时<0.1秒,比之前快98%;
- 外部数据加载从秒级变毫秒级,快99%;
- 工具调用次数少了30%,网络错误率从7.14%降到0;
- 测试通过率从92.86%涨到100%。