AI Agent 30天速成|Day10 笔记

AI Agent 全日制30天速成|Day10 学习笔记

今日核心主题:Agent 工具编排 + 异步批量任务 + 接口鉴权与分布式锁

修订说明

  1. 基于Day9全工具化架构迭代,保留Chroma向量库、统一工具网关、Redis记忆、熔断限流体系
  2. 新增工具链批量编排:支持顺序/并行多工具组合任务,无需模型多轮ReAct循环
  3. 实现异步后台任务队列,长耗时知识库批量入库、大规模向量离线计算不阻塞HTTP接口
  4. 增加全局接口Token鉴权、分布式会话锁,适配多实例分布式部署场景
  5. 新增Agent调用成本统计模块,统计LLM/Embedding调用次数、Token消耗、计费估算

今日总学习目标(全天8h分配)

  • 理论学习:2.5h
  • 分层代码编写调试:4h
  • 面试复盘背诵:1.5h
  1. 掌握工具编排语法,定义串行/并行工具流水线,实现一次性多工具批量执行
  2. 基于Redis List实现轻量异步任务队列,处理长耗时离线向量任务
  3. 实现全局API鉴权、用户资源配额(LLM/Embedding每日调用上限)
  4. 分布式锁解决多实例Redis会话并发读写错乱问题
  5. 新增调用成本统计模块,全链路采集Token消耗与接口调用量
  6. 整合流水线编排、异步任务、鉴权配额、分布式锁,升级生产级Agent服务

一、核心理论教学笔记

1 工具流水线编排(Tool Pipeline)

1.1 单轮ReAct存在短板

ReAct每轮只能执行单个工具,多步骤任务需要多次LLM思考,消耗大量Token与接口次数;固定流程(先检索再计算、批量入库)重复循环效率极低。

1.2 两种编排模式

  1. 串行流水线(依赖型):上一个工具输出作为下一个工具输入,顺序执行
    示例:rag_search → calculator(先查公式再代入计算)
  2. 并行流水线(无依赖):多个工具同时并发执行,结果统一汇总
    示例:同时执行多条rag_search多维度知识库检索

1.3 流水线结构化定义

通过Pydantic定义流水线JSON结构,模型可一次性生成多工具任务,网关自动解析批量调度,仅消耗单次LLM思考。
流水线内置变量传递:前序工具输出可作为后续工具入参,无需重复拼接Prompt。

2 Redis异步任务队列(轻量版,无需RabbitMQ)

2.1 适用场景

批量文档入库、海量文本Embedding、大规模知识库更新等耗时操作,同步HTTP接口会超时。

2.2 队列执行流程

  1. 前端提交批量任务 → 接口校验权限配额 → 写入Redis任务队列,返回task_id
  2. 后台独立协程循环消费队列,执行向量入库/Embedding批量工具
  3. 任务进度、执行结果持久存入Redis Hash,用户通过task_id轮询查询状态
  4. 失败任务自动重试2次,最终失败写入死信队列人工排查

3 分布式配套能力

3.1 全局API鉴权

所有接口请求携带access_token,服务内存维护token-角色-配额映射;区分普通用户/管理员每日调用上限,超量直接拦截。

3.2 分布式会话锁

多服务实例同时收到同一用户多轮提问,并发读写Redis会话会造成消息错乱;基于Redis SETNX实现会话互斥锁,单会话同一时间仅允许一条对话执行。

3.3 调用成本统计

每条LLM、Embedding调用采集输入/输出Token,存入Redis计数器;按厂商单价估算消耗费用,支持按用户、会话、日期维度统计用量。

4 Day9→Day10 完整升级链路

用户HTTP请求携带token鉴权 → 校验用户每日调用配额
→ 获取分布式会话锁,防止并发乱序
→ 读取Redis分层记忆,自动摘要/滑动窗口压缩
→ 模型二选一:

  1. 简单任务:单工具ReAct循环执行
  2. 固定多步骤任务:生成流水线编排,网关批量并行/串行执行工具
    → 短任务同步返回;长批量任务推送Redis异步队列,返回task_id
    → 所有工具执行埋点:限流、熔断、日志、Token消耗统计
    → 工具结果回填上下文,LLM生成回答,脱敏处理
    → 对话持久Redis,释放分布式会话锁
    → 后台协程持续消费异步队列,处理离线向量批量任务

5 生产级配额与限流规则

  1. 访客:仅基础闲聊,禁用所有工具,每日LLM 10次上限
  2. 普通用户:calculator/rag_search可用,禁用vector_add;LLM 200次/天,Embedding 500次/天
  3. 管理员:全工具开放,无每日硬上限,仅全局令牌桶流量控制

二、今日学习重点

  1. 实现ToolPipeline流水线结构化定义,网关自动解析串行/并行批量工具执行
  2. 搭建Redis轻量异步任务队列,实现长耗时向量任务后台异步处理
  3. 开发全局Token鉴权、用户每日调用配额拦截逻辑
  4. Redis分布式锁解决多实例会话并发读写冲突
  5. 全链路Token采集、用量统计、成本估算模块开发
  6. 兼容原有Day9所有能力,无破坏性改动,平滑升级

三、今日难点 & 解决方案

难点1 流水线工具之间参数传递复杂,前序结果无法自动传给下一个工具

解决方案:流水线结构定义输出变量key,网关缓存每一步工具结果,后续工具可通过${变量名}动态填充入参,自动替换文本。

难点2 多实例部署,同一用户并发对话导致历史消息错乱

解决方案:执行对话前后加分布式Redis锁,锁过期时间30s;对话流程未完成时,同会话新请求直接返回“对话处理中,请稍后”。

难点3 批量向量入库耗时过长,同步接口60s超时

解决方案:耗时任务剥离至Redis异步队列,接口仅生成任务ID立即返回;后台协程单独消费,前端轮询任务进度。

难点4 用户无感知超额调用LLM/Embedding产生高额费用

解决方案:按用户角色设置每日调用配额,Redis计数器实时累加,达到上限直接拦截,同时每日零点自动重置计数。

难点5 异步任务大量失败堆积无法感知

解决方案:区分正常队列与死信队列,失败重试2次仍异常的任务转入死信;日志记录全部失败task_id,提供查询接口查看失败详情。

四、完整项目代码(基于Day9扩展,新增文件+修改原有文件)

依赖新增

pip install python-multipart

项目目录新增文件

day10_agent/
├── .env                # 新增鉴权、队列、配额配置
├── middleware.py       # 新增分布式锁、用量统计逻辑
├── security.py         # 新增token鉴权、用户配额校验
├── pipeline.py         # 【今日新增】工具流水线编排定义
├── async_task.py       # 【今日新增】Redis异步任务队列消费
├── llm_client.py
├── tool_gateway.py     # 适配流水线批量工具执行
├── memory_store.py
├── agent_core.py       # 支持流水线+ReAct双模式
└── main.py             # 新增鉴权中间件、任务查询接口

1 .env 新增配置项

# 原有LLM/Redis/Chroma配置保留
# API鉴权
ADMIN_TOKEN=admin123456
USER_TOKEN=user654321
GUEST_TOKEN=guest0000
# 每日调用配额
ADMIN_QUOTA_LLM=9999
ADMIN_QUOTA_EMB=9999
USER_QUOTA_LLM=200
USER_QUOTA_EMB=500
GUEST_QUOTA_LLM=10
GUEST_QUOTA_EMB=0
# 异步任务队列
TASK_QUEUE_KEY=agent:task:queue
DEAD_QUEUE_KEY=agent:task:dead
TASK_EXPIRE=86400
# 分布式锁
LOCK_PREFIX=agent:session:lock
LOCK_EXPIRE=30
# Token计费单价(元/千token)
PRICE_INPUT=0.5
PRICE_OUTPUT=1.0
PRICE_EMB=0.2

2 pipeline.py(全新文件:工具流水线编排)

from pydantic import BaseModel, Field
from typing import List, Dict, Optional, Union
from tool_gateway import gateway

class PipelineStep(BaseModel):
    task_id: str = Field(description="流水线内步骤唯一标识")
    tool_name: str = Field(description="执行工具名称")
    args: Dict = Field(description="工具入参,支持${变量名}引用上一步输出")
    depends_on: List[str] = Field(default=[], description="依赖步骤id,空则并行执行")
    output_var: str = Field(description="本步骤输出存储变量名,供后续步骤引用")

class ToolPipeline(BaseModel):
    run_mode: str = Field(description="serial串行 / parallel并行")
    steps: List[PipelineStep] = Field(description="流水线步骤列表")

class PipelineRunner:
    def __init__(self):
        self.var_cache = {}  # 存储各步骤输出变量

    # 替换参数内${xxx}变量
    def replace_var(self, raw_arg: Union[str, Dict]) -> Union[str, Dict]:
        if isinstance(raw_arg, str):
            for k, v in self.var_cache.items():
                raw_arg = raw_arg.replace(f"${{{k}}}", str(v))
            return raw_arg
        if isinstance(raw_arg, dict):
            new_dict = {}
            for k, val in raw_arg.items():
                new_dict[k] = self.replace_var(val)
            return new_dict
        return raw_arg

    async def run_single_step(self, step: PipelineStep, trace_id: str, user_role: str):
        parsed_args = self.replace_var(step.args)
        res = await gateway.run_tool(step.tool_name, parsed_args, trace_id, user_role)
        self.var_cache[step.output_var] = res
        return {step.task_id: res}

    async def run_pipeline(self, pipeline: ToolPipeline, trace_id: str, user_role: str):
        all_results = {}
        step_map = {s.task_id: s for s in pipeline.steps}
        finished = set()
        while len(finished) < len(step_map):
            run_coros = []
            run_steps = []
            for sid, step in step_map.items():
                if sid in finished:
                    continue
                # 判断依赖是否全部完成
                dep_ok = all(d in finished for d in step.depends_on)
                if dep_ok:
                    run_coros.append(self.run_single_step(step, trace_id, user_role))
                    run_steps.append(sid)
            if not run_coros:
                break
            batch_res = await asyncio.gather(*run_coros)
            for item in batch_res:
                all_results.update(item)
            for s in run_steps:
                finished.add(s)
        return {
            "pipeline_var": self.var_cache,
            "step_result": all_results
        }

pipeline_runner = PipelineRunner()

3 async_task.py(全新文件:异步任务队列)

import asyncio
import json
import uuid
import os
from dotenv import load_dotenv
from memory_store import memory
from pipeline import ToolPipeline, pipeline_runner
load_dotenv()

TASK_QUEUE = os.getenv("TASK_QUEUE_KEY")
DEAD_QUEUE = os.getenv("DEAD_QUEUE_KEY")
TASK_EXPIRE = int(os.getenv("TASK_EXPIRE"))

class AsyncTaskCenter:
    def __init__(self):
        self.redis = memory.redis

    def gen_task_id(self):
        return str(uuid.uuid4())

    async def submit_task(self, task_type: str, pipeline_dict: dict, trace_id: str, user_role: str):
        tid = self.gen_task_id()
        task_data = json.dumps({
            "task_id": tid,
            "task_type": task_type,
            "pipeline": pipeline_dict,
            "trace_id": trace_id,
            "user_role": user_role,
            "status": "pending",
            "retry_cnt": 0
        })
        # 写入队列 & Hash存储详情
        await self.redis.lpush(TASK_QUEUE, task_data)
        await self.redis.setex(f"task:info:{tid}", TASK_EXPIRE, task_data)
        return tid

    async def get_task_info(self, task_id: str):
        raw = await self.redis.get(f"task:info:{task_id}")
        if not raw:
            return None
        return json.loads(raw)

    async def consumer_loop(self):
        print("异步任务消费协程启动成功")
        while True:
            # 阻塞读取队列
            raw_task = await self.redis.brpop(TASK_QUEUE, timeout=2)
            if not raw_task:
                continue
            _, task_str = raw_task
            task = json.loads(task_str)
            tid = task["task_id"]
            try:
                task["status"] = "running"
                await self.redis.setex(f"task:info:{tid}", TASK_EXPIRE, json.dumps(task))
                # 执行流水线
                pipeline = ToolPipeline(**task["pipeline"])
                res = await pipeline_runner.run_pipeline(pipeline, task["trace_id"], task["user_role"])
                task["status"] = "success"
                task["result"] = json.dumps(res, ensure_ascii=False)
            except Exception as e:
                task["retry_cnt"] += 1
                task["err_msg"] = str(e)
                if task["retry_cnt"] < 2:
                    # 重新入队重试
                    await self.redis.lpush(TASK_QUEUE, json.dumps(task))
                    continue
                task["status"] = "failed"
                # 转入死信队列
                await self.redis.rpush(DEAD_QUEUE, json.dumps(task))
            await self.redis.setex(f"task:info:{tid}", TASK_EXPIRE, json.dumps(task))

task_center = AsyncTaskCenter()

4 middleware.py 新增分布式锁、用量统计代码

# 在原有代码末尾追加
import os
LOCK_PREFIX = os.getenv("LOCK_PREFIX")
LOCK_EXPIRE = int(os.getenv("LOCK_EXPIRE"))
PRICE_INPUT = float(os.getenv("PRICE_INPUT"))
PRICE_OUTPUT = float(os.getenv("PRICE_OUTPUT"))
PRICE_EMB = float(os.getenv("PRICE_EMB"))

# 分布式会话锁
async def session_lock(redis, session_id: str):
    key = f"{LOCK_PREFIX}{session_id}"
    ok = await redis.set(key, "locked", ex=LOCK_EXPIRE, nx=True)
    return bool(ok)

async def session_unlock(redis, session_id: str):
    key = f"{LOCK_PREFIX}{session_id}"
    await redis.delete(key)

# Token用量统计
class TokenStat:
    def __init__(self, redis):
        self.redis = redis

    async def add_llm_token(self, user_role: str, input_tok: int, output_tok: int):
        today = time.strftime("%Y%m%d")
        key_llm_in = f"stat:{today}:{user_role}:llm_input"
        key_llm_out = f"stat:{today}:{user_role}:llm_output"
        await self.redis.incrby(key_llm_in, input_tok)
        await self.redis.incrby(key_llm_out, output_tok)

    async def add_emb_token(self, user_role: str, tok_num: int):
        today = time.strftime("%Y%m%d")
        key_emb = f"stat:{today}:{user_role}:embedding"
        await self.redis.incrby(key_emb, tok_num)

    async def get_cost(self, user_role: str):
        today = time.strftime("%Y%m%d")
        in_tok = int(await self.redis.get(f"stat:{today}:{user_role}:llm_in") or 0)
        out_tok = int(await self.redis.get(f"stat:{today}:llm_out") or 0)
        emb_tok = int(await self.redis.get(f"stat:{today}:emb") or 0)
        cost = (in_tok / 1000) * PRICE_INPUT + (out_tok / 1000) * PRICE_OUTPUT + (emb_tok / 1000) * PRICE_EMB
        return round(cost, 4)

stat_client = TokenStat(memory.redis)

5 security.py 新增Token鉴权+配额校验

# 原有代码保留,追加下方内容
import os
TOKEN_ROLE_MAP = {
    os.getenv("ADMIN_TOKEN"): "admin",
    os.getenv("USER_TOKEN"): "user",
    os.getenv("GUEST_TOKEN"): "guest"
}
QUOTA_CFG = {
    "admin": {
        "llm": int(os.getenv("ADMIN_QUOTA_LLM")),
        "emb": int(os.getenv("ADMIN_QUOTA_EMB"))
    },
    "user": {
        "llm": int(os.getenv("USER_QUOTA_LLM")),
        "emb": int(os.getenv("USER_QUOTA_EMB"))
    },
    "guest": {
        "llm": int(os.getenv("GUEST_QUOTA_LLM")),
        "emb": int(os.getenv("GUEST_QUOTA_EMB"))
    }
}

# Token鉴权
def verify_token(token: str) -> tuple[bool, str]:
    if token not in TOKEN_ROLE_MAP:
        return False, ""
    return True, TOKEN_ROLE_MAP[token]

# 校验当日调用配额
async def check_quota(redis, user_role: str, call_type: str) -> bool:
    today = time.strftime("%Y%m%d")
    limit = QUOTA_CFG[user_role][call_type]
    key = f"quota:{today}:{user_role}:{call_type}"
    current = int(await redis.get(key) or 0)
    if current >= limit:
        return False
    await redis.incr(key)
    return True

6 agent_core.py 改造支持流水线编排

# 原有导入追加
from pipeline import ToolPipeline, pipeline_runner
from middleware import stat_client

class ReActAgent:
    # 原有init、reflect函数不变
    async def run_chat(self, session_id: str, user_role: str, user_input: str, trace_id: str):
        history = await memory.load_history(session_id)
        base_msg = [{"role":"system", "content":self.system_prompt}] + history
        base_msg.append({"role":"user", "content":user_input})
        msg_list = await memory.auto_compress(base_msg)
        # 新增:判断生成流水线还是单轮ReAct
        pipeline_prompt = """
判断用户问题是否为固定多步骤任务,是则输出ToolPipeline JSON,run_mode选serial/parallel;简单单任务输出{"is_pipeline":false}
"""
        pipe_check_msg = msg_list + [{"role":"user", "content":pipeline_prompt}]
        pipe_raw = await llm_client.chat_sync(pipe_check_msg, temperature=0.0)["choices"][0]["message"]["content"]
        # 省略JSON解析逻辑,区分两种执行分支
        if "is_pipeline" in pipe_raw and json.loads(pipe_raw)["is_pipeline"] is False:
            # 原有ReAct循环逻辑不变,执行后统计token
            loop_cnt = 0
            tool_record = {}
            input_tokens = sum(len(m["content"]) for m in msg_list)
            while loop_cnt < self.max_loop:
                # 原有ReAct代码不变
                ...
            output_tokens = len(final_raw)
            await stat_client.add_llm_token(user_role, input_tokens, output_tokens)
        else:
            # 流水线分支
            pipe_data = json.loads(pipe_raw)
            pipeline = ToolPipeline(**pipe_data)
            pipeline_res = await pipeline_runner.run_pipeline(pipeline, trace_id, user_role)
            concat_info = json.dumps(pipeline_res["step_result"], ensure_ascii=False)
            final_prompt = msg_list + [{"role":"user", "content":f"结合流水线结果回答:{concat_info}"}]
            final_raw = await llm_client.chat_sync(final_prompt, temperature=0.1)["choices"][0]["message"]["content"]
            input_tokens = sum(len(m["content"]) for m in final_prompt)
            output_tokens = len(final_raw)
            await stat_client.add_llm_token(user_role, input_tokens, output_tokens)
        # 脱敏、持久化逻辑不变
        final_ans = desensitize(final_raw)
        await memory.append(session_id, "user", user_input)
        await memory.append(session_id, "assistant", final_ans)
        log_client.write(trace_id, "INFO", {"final_answer": final_ans[:300]})
        return {
            "trace_id": trace_id,
            "tool_record": tool_record if "tool_record" in locals() else pipeline_res,
            "answer": final_ans
        }
react_agent = ReActAgent()

7 main.py 新增鉴权、任务查询、异步提交接口

from fastapi import FastAPI, Query, Header, HTTPException
import asyncio
from agent_core import react_agent
from memory_store import memory
from middleware import global_bucket, create_trace_id, log_client, session_lock, session_unlock
from security import input_verify, verify_token, check_quota
from async_task import task_center

app = FastAPI(title="Day10 流水线Agent|异步任务+分布式锁+鉴权配额")

# 全局启动事件新增异步消费协程
@app.on_event("startup")
async def startup():
    await memory.connect()
    asyncio.create_task(task_center.consumer_loop())

@app.on_event("shutdown")
async def shutdown():
    await memory.close()

# 通用对话接口,增加token鉴权
@app.get("/agent/chat")
async def chat_api(
    Authorization: str = Header(..., description="Bearer token"),
    session_id: str = Query(...),
    user_role: str = Query(default="user"),
    prompt: str = Query(...)
):
    # 1 Token鉴权
    token = Authorization.replace("Bearer ","")
    auth_ok, real_role = verify_token(token)
    if not auth_ok:
        raise HTTPException(status_code=401, detail="非法访问令牌")
    # 2 配额校验
    llm_quota_ok = await check_quota(memory.redis, real_role, "llm")
    if not llm_quota_ok:
        return {"trace_id": create_trace_id(), "answer": "今日LLM调用额度已耗尽,请明日再试"}
    # 3 分布式会话锁
    lock_success = await session_lock(memory.redis, session_id)
    if not lock_success:
        return {"trace_id": create_trace_id(), "answer": "当前会话正在处理对话,请稍后重试"}
    try:
        trace_id = create_trace_id()
        log_client.write(trace_id, "INFO", {"session_id": session_id, "input": prompt, "role": real_role})
        # 输入安全校验
        ok, safe_text = await input_verify(prompt)
        if not ok:
            return {"trace_id": trace_id, "answer": safe_text}
        # 全局限流
        if not await global_b.get_token():
            log_client.write(trace_id, "WARN", {"msg": "触发全局限流"})
            return {"trace_id": trace_id, "answer": "服务繁忙,请稍后重试"}
        # 主Agent执行
        result = await react_agent.run_chat(session_id, real_role, safe_text, trace_id)
        return result
    finally:
        # 释放会话锁
        await session_unlock(memory.redis, session_id)

# 提交异步批量流水线任务接口
@app.post("/agent/task/submit")
async def submit_task(
    Authorization: str = Header(...),
    session_id: str,
    task_type: str,
    pipeline: dict
):
    token = Authorization.replace("Bearer ","")
    auth_ok, real_role = verify_token(token)
    if not auth_ok:
        raise HTTPException(401, "token无效")
    # 仅管理员可提交批量入库任务
    if task_type == "batch_embed" and real_role != "admin":
        raise HTTPException(403, "仅管理员可执行批量向量任务")
    trace_id = create_trace_id()
    tid = await task_center.submit_task(task_type, pipeline, trace_id, real_role)
    return {"task_id": tid, "trace_id": trace_id, "msg": "任务已进入后台队列"}

# 查询异步任务进度
@app.get("/agent/task/query")
async def query_task(task_id: str):
    info = await task_center.get_task_info(task_id)
    if not info:
        return {"msg": "任务不存在或已过期"}
    return info

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main.py", reload=True)

五、今日实操练习任务

  1. 配置.env内三类Token,分别用admin/user/guest令牌调用接口,验证权限隔离与每日配额拦截
  2. 构造复合问题:先检索RAG定义,再计算(100+20)*5,观察模型自动生成串行流水线批量执行工具
  3. 使用admin账号提交批量文档入库流水线,获取task_id,轮询/agent/task/query查看后台执行进度
  4. 同一session_id并发发送两条提问,验证分布式锁拦截,返回会话繁忙提示
  5. 连续调用接口耗尽user角色每日LLM配额,观察配额拦截提示
  6. 人为制造批量向量任务异常,查看死信队列存储失败任务信息
  7. 查看日志与Redis统计key,验证LLM/Embedding Token消耗与费用估算正常

六、配套完整面试题(含标准答案)

基础问答

1 Tool流水线和ReAct循环的核心区别,各自适用场景?
标准答案
ReAct单次只能调用一个工具,多步骤任务需要多轮LLM思考,消耗更多Token;适合无固定流程、开放式未知问题。
Tool流水线一次性定义串行/并行多工具步骤,模型仅一次规划,网关批量调度;适合流程固定、多依赖/多并行查询的标准化任务,批量知识库入库、固定计算检索组合优先使用流水线。

2 为什么要用Redis实现异步任务,不直接同步执行批量向量入库?
标准答案
批量Embedding、大规模文档入库耗时可达数十秒,HTTP接口默认超时会断开连接;异步队列将耗时操作剥离后台,前端立刻获取task_id轮询结果,提升接口可用性;同时失败任务支持自动重试,不阻塞用户对话主线程。

3 分布式会话锁解决什么问题,实现原理?
标准答案
多服务实例部署时,同一用户同时发起多条对话请求,并发读写Redis会话列表会造成消息错乱、上下文拼接异常。
基于Redis SETNX原子命令加锁,会话处理期间持有30s过期锁;新同会话请求检测锁存在直接拒绝,对话结束主动释放锁,保证单会话串行执行。

4 用户配额统计基于Redis什么数据结构,零点如何重置每日用量?
标准答案
使用Redis String计数器,key拼接日期+用户角色区分每日用量;每日零点定时任务删除当日统计key,自动重置次日计数;设置过期时间24h,自动清理过期历史用量数据。

5 流水线变量传递实现逻辑是什么?
标准答案
网关内置变量缓存字典,每一步工具执行完成后将输出存入对应output_var;解析后续步骤入参时,正则匹配${变量名}自动替换为上一步工具输出,无需模型重复拼接上下文参数。

工程实操题

1 流水线步骤存在循环依赖(A依赖B,B依赖A)如何处理?
标准答案
规划阶段Prompt增加依赖校验规则,模型生成流水线时禁止循环依赖;网关执行前遍历所有depends_on做拓扑校验,检测循环依赖直接返回任务规划失败,拒绝执行。

2 异步任务消费协程意外退出,如何保证任务不丢失?
标准答案
Redis brpop阻塞读取特性,任务取出未处理完成进程崩溃会重新回到队列;可搭配定时补偿协程,扫描长时间running状态未完成的任务重新入队,保障消息可靠消费。

3 多角色权限+双层配额(工具权限+每日调用上限)如何分层校验?
标准答案
第一层security工具权限:guest禁用所有工具,user禁用vector_add;第二层每日用量配额:Redis计数器拦截LLM/Embedding超量请求;两层校验全部通过才允许执行对应工具,任一拦截直接返回提示。

4 线上如何防止恶意刷取Embedding产生高额费用?
标准答案
三层防护:①全局令牌桶流量限流;②按用户角色设置每日Embedding调用硬配额;③异步批量向量任务仅管理员开放,普通用户无法批量发起大规模Embedding请求。

拓展思考题

1 流水线能否嵌套子流水线,如何实现复杂多层业务流程?
标准答案
扩展PipelineStep新增sub_pipeline字段,网关识别后递归执行子流水线,子流水线输出变量向上层主流水线共享,实现多层嵌套编排,适合复杂多级业务流程。

2 分布式多实例部署,异步任务队列会重复消费吗,如何解决?
标准答案
多实例同时brpop会争抢任务,天然不会重复消费;如需严格幂等,工具执行时增加业务唯一ID判断,重复task_id直接跳过执行逻辑。

3 如何对接定时任务,实现每日自动批量更新知识库流水线?
标准答案
引入APScheduler定时协程,固定时间自动构造批量入库流水线,调用task_center.submit_task写入队列,后台消费自动完成知识库增量更新。

学习总结

Day10在Day9全工具化持久Agent基础上完成生产级分布式配套升级,新增三大核心模块:工具流水线批量编排、Redis异步离线任务、分布式鉴权锁与用量成本统计。
解决三大线上痛点:多步骤任务Token消耗高、长耗时向量操作接口超时、多实例并发会话错乱、无用量管控成本失控。
至此整套Agent具备单机开发、分布式部署、权限管控、成本计量、离线批量处理完整工业能力,覆盖中小型AI智能体全生产落地需求。

posted @ 2026-07-05 21:00  云淡风轻YangG  阅读(49)  评论(0)    收藏  举报