首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >大模型应用开发:从 Prompt 工程到 Agent 系统的架构跃迁

大模型应用开发:从 Prompt 工程到 Agent 系统的架构跃迁

原创
作者头像
用户12339161
修改2026-08-03 16:51:41
修改2026-08-03 16:51:41
1870
举报

大模型应用开发:从 Prompt 工程到 Agent 系统的架构跃迁

当你发现拼接 Prompt 已经无法让模型输出稳定格式,当 RAG 检索出的文档大半是噪声,当单轮问答无法完成“订机票 + 查天气 + 写邮件”的复合任务——恭喜你,你的大模型应用进入了“深水区”。 本文不教你怎么调用 API,而是从确定性输出、RAG 精准召回、Agent 规划与记忆、成本与延迟优化四个硬核维度,系统梳理一条从“Demo 玩具”到“生产级 Agent”的演进路径。所有方案均来自真实业务场景的反复试错。


1. 确定性输出:让 JSON 稳定得像传统接口

大模型的“随机性”是应用开发的第一道坎。业务方要求返回 {"name": "张三", "age": 25},模型却给你 “好的,以下是 JSON 格式...” 加上 Markdown 代码块。

1.1 结构化生成的三重保障

层级一:Prompt 约束(最脆弱)
代码语言:javascript
复制
“你必须只返回一个合法的 JSON 对象,不要包含任何解释、前后缀或 Markdown 标记。”

效果:约 70% 的请求能直接命中,但复杂 Schema 下失败率飙升。

层级二:约束解码(Constrained Decoding)

使用 JSON Schema 强制生成,在采样阶段屏蔽不符合语法规则的 Token。推荐 outlines 库(比 jsonformer 更稳定):

代码语言:javascript
复制
import outlines

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer", "minimum": 0, "maximum": 120},
        "hobbies": {"type": "array", "items": {"type": "string"}}
    },
    "required": ["name", "age"]
}

model = outlines.models.transformers("Qwen/Qwen2-7B-Instruct")
generator = outlines.generate.json(model, schema)

result = generator("提取以下文本中的姓名、年龄和爱好:张三,25岁,喜欢编程和游泳。")
print(result)  # {'name': '张三', 'age': 25, 'hobbies': ['编程', '游泳']}

原理:通过动态修改 logits,只允许生成符合 Schema 的 Token,从根源上杜绝非法 JSON。

层级三:后处理鲁棒性(兜底)

即使使用约束解码,也可能因为模型未输出完整内容而解析失败。我们封装了一个“解析 + 修复”函数:

代码语言:javascript
复制
import json
import re

def safe_json_parse(text: str) -> dict:
    # 1. 尝试直接解析
    try:
        return json.loads(text)
    except:
        pass
    # 2. 移除 Markdown 代码块
    cleaned = re.sub(r'```json\s*|\s*```', '', text)
    # 3. 使用正则提取第一个完整 JSON 对象
    match = re.search(r'\{.*\}', cleaned, re.DOTALL)
    if match:
        try:
            return json.loads(match.group())
        except:
            pass
    # 4. 利用 json-repair 修复常见错误(如末尾逗号)
    from json_repair import repair_json
    return json.loads(repair_json(cleaned))

2. RAG 进阶:从“向量检索”到“混合搜索 + 重排序”

大多数 RAG 教程只教你把文档切块、Embedding、存向量库。但在生产环境中,召回精度上下文相关度才是决定生成质量的命门。

2.1 多路召回:BM25 + 向量 + 关键词

单一的向量检索对罕见词(如特定产品型号)极不友好,而 BM25 擅长精确匹配。我们采用 倒排索引(Elasticsearch) + 向量索引(FAISS) 并行召回,然后融合排序。

代码语言:javascript
复制
from elasticsearch import Elasticsearch
import faiss
import numpy as np

def multi_recall(query: str, top_k: int = 10):
    # 1. BM25 召回(ES)
    es_body = {
        "query": {
            "multi_match": {"query": query, "fields": ["title^3", "content"], "operator": "or"}
        },
        "size": top_k * 2
    }
    es_hits = es.search(index="docs", body=es_body)["hits"]["hits"]
    
    # 2. 向量召回(FAISS)
    query_vec = embed_model.encode(query).reshape(1, -1)
    distances, indices = faiss_index.search(query_vec, top_k * 2)
    vec_docs = [all_docs[i] for i in indices[0]]
    
    # 3. 合并去重 + RRF(Reciprocal Rank Fusion)重排
    scores = {}
    for rank, hit in enumerate(es_hits):
        doc_id = hit["_id"]
        scores[doc_id] = scores.get(doc_id, 0) + 1 / (60 + rank)
    for rank, doc in enumerate(vec_docs):
        doc_id = doc["id"]
        scores[doc_id] = scores.get(doc_id, 0) + 1 / (60 + rank)
    
    sorted_docs = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_k]
    return [doc_map[d] for d, _ in sorted_docs]

2.2 重排序(Rerank)—— 用交叉编码器做最终筛选

向量召回往往包含大量“语义相近但实则无关”的文档。我们引入一个轻量级的交叉编码器(Cross-Encoder),如 BAAI/bge-reranker-v2-m3,对前 N 个候选进行精排。虽然它比向量模型慢,但只在 top 20 上运行,总体延迟可控。

代码语言:javascript
复制
from sentence_transformers import CrossEncoder

reranker = CrossEncoder('BAAI/bge-reranker-v2-m3', max_length=512)

def rerank_docs(query: str, docs: list, top_k: int = 5):
    pairs = [[query, d["content"]] for d in docs]
    scores = reranker.predict(pairs)  # 返回相关度分数
    sorted_idx = np.argsort(scores)[::-1]
    return [docs[i] for i in sorted_idx[:top_k]]

效果:在客服场景中,Top-5 准确率从 65% 提升至 91%,显著减少幻觉。

2.3 上下文窗口管理:防止“中间丢失”

即使使用了重排序,把 10 个冗长文档全部塞进上下文,不仅浪费 Token,还会因为Lost-in-the-Middle问题导致模型忽略中间内容。我们采用摘要压缩 + 位置加权策略:

代码语言:javascript
复制
def compress_docs(docs, max_tokens=2000):
    # 将每个文档用 LLM 生成一句关键句(利用较小的模型,如 GPT-3.5-turbo)
    summaries = []
    for d in docs:
        prompt = f"用一句话概括以下内容:{d['content'][:300]}"
        summary = small_llm.generate(prompt)
        summaries.append(summary)
    # 将摘要拼接,并保证总长度不超过 max_tokens
    return " ".join(summaries)[:max_tokens]

3. Agent 系统:从“一问一答”到“任务闭环”

Agent 是大模型应用的高级形态,需要规划(Planning) + 工具调用(Tool Use) + 记忆(Memory)。我们开发了一个用于“旅行规划”的多 Agent 系统。

3.1 工具定义与调用规范

使用 OpenAI Function Calling 格式,兼容大多数开源模型(如 Qwen、GLM)。

代码语言:javascript
复制
tools = [
    {
        "type": "function",
        "function": {
            "name": "search_flights",
            "description": "查询航班信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "origin": {"type": "string"},
                    "destination": {"type": "string"},
                    "date": {"type": "string", "format": "date"}
                },
                "required": ["origin", "destination"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询目的地天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        }
    }
]

3.2 ReAct 循环:推理与行动交织

我们实现一个轻量级 ReAct 引擎,支持多轮工具调用和结果聚合。

代码语言:javascript
复制
class SimpleAgent:
    def __init__(self, model, tools, max_iter=5):
        self.model = model
        self.tools = tools
        self.max_iter = max_iter
        self.memory = []  # 对话历史

    def run(self, user_query):
        self.memory.append({"role": "user", "content": user_query})
        for _ in range(self.max_iter):
            response = self.model.chat(self.memory, tools=self.tools)
            # 检查是否要求调用工具
            if response.get("tool_calls"):
                for call in response["tool_calls"]:
                    tool_name = call["function"]["name"]
                    args = json.loads(call["function"]["arguments"])
                    result = self.execute_tool(tool_name, args)
                    self.memory.append({
                        "role": "tool",
                        "tool_call_id": call["id"],
                        "content": json.dumps(result)
                    })
            else:
                # 没有工具调用,直接返回最终答案
                self.memory.append({"role": "assistant", "content": response["content"]})
                return response["content"]
        return "任务执行超时,请简化需求。"

关键优化

  • 工具并发调用:当 Agent 一次性决定调用多个独立工具(如查航班和查天气),我们并行执行而非串行,节省 50% 等待时间。
  • 思考过程压缩:不要将完整的工具返回 JSON 全部塞入上下文,只提取关键字段,防止上下文膨胀。

3.3 长期记忆:向量数据库存储用户偏好

对于个人助手类 Agent,我们需要记住用户的历史偏好(如“喜欢靠窗座位”、“不吃海鲜”)。每次调用 Agent 时,先从向量库检索该用户的近期记忆,注入 system prompt。

代码语言:javascript
复制
user_memories = vector_db.search(user_id, top_k=5)  # 返回偏好条目
memory_prompt = "该用户的历史偏好:" + ";".join([m["content"] for m in user_memories])
system_prompt = f"你是一个智能助手。{memory_prompt}。请根据用户当前需求提供个性化服务。"

4. 延迟与成本优化:让 LLM 应用“跑得动、用得起”

LLM 推理的费用和延迟是商业落地的最大障碍。我们通过以下组合拳实现降本增效:

4.1 提示词缓存(Prompt Caching)

对于固定的系统指令和少量固定文档,我们利用模型的 KV Cache 复用(如 OpenAI 的 Prompt Caching 或开源的 cache 机制)。若前缀完全相同,则不需重算 Attention 的 Key/Value。

4.2 路由策略:大模型与小模型的动态选择

并非所有请求都需要“满血版”模型。我们在网关层增加一个意图分类器(轻量级 BERT),将简单问答路由到 7B 模型(延迟 200ms,费用低 10 倍),复杂推理路由到 70B 模型。

代码语言:javascript
复制
class Router:
    def __init__(self):
        self.classifier = pipeline("text-classification", model="small-intent-model")
    
    def route(self, query):
        label = self.classifier(query)[0]["label"]
        if label in ["问候", "常识", "翻译"]:
            return "qwen2-7b"
        else:
            return "qwen2-72b"

4.3 流式输出与首字延迟优化

对于交互式应用(如聊天),TTFT 至关重要。我们强制使用流式输出(SSE),且开启 Speculative Decoding(推测解码):用小模型快速生成候选 Token,大模型只做验证,可将生成速度提升 2~3 倍。

代码语言:javascript
复制
# vLLM 支持推测解码
llm = LLM(model="meta-llama/Llama-2-70b-chat",
          speculative_model="meta-llama/Llama-2-7b-chat",
          num_speculative_tokens=5)  # 每次预测 5 个候选

5. 可观测性:给大模型应用装上“黑匣子”

大模型应用的错误难以复现,必须建立完善的可观测体系。

5.1 记录关键事件(LangSmith / 自建)

每次请求记录:

  • prompt 完整内容(含 system 和 tools)
  • responsetool_calls
  • token_usage(input / output / total)
  • latency_breakdown(检索耗时、模型推理耗时、工具执行耗时)
  • feedback(用户点赞/点踩,用于后续 RLHF)

5.2 评估与回归测试(LLM-as-Judge)

构建一个包含 200+ 典型问题的测试集,每次模型升级或 Prompt 修改后,自动运行评估,用更强的模型(如 GPT-4)对输出质量打分(准确性、相关性、格式合规),低于阈值则阻断上线。

代码语言:javascript
复制
def evaluate_answer(query, answer, reference=None):
    judge_prompt = f"""你是一个严格的评审专家。请对以下回答进行评分(1-10分):
    问题:{query}
    回答:{answer}
    参考:{reference or "无"}
    请返回 JSON:{{"score": 8, "reason": "..."}}"""
    return llm_generate(judge_prompt, model="gpt-4")

6. 实战案例:智能客服 Agent 的迭代之路

背景:某电商平台需要将人工客服的 40% 问答自动化。

V1(原始方案):直接 Prompt + 向量检索(Q&A 对),准确率 62%,用户满意度 3.2/5。

V2(结构化输出 + 混合召回):强制 JSON 返回意图和实体,召回引入 BM25+向量,准确率 78%,满意度 3.8/5。

V3(Agent + 工具):允许 Agent 调用订单查询、退换货申请、物流追踪等 6 个 API,准确率 89%,满意度 4.2/5。但平均延迟从 1.2s 升至 3.5s。

V4(优化版)

  • 路由分类器将简单 FAQ 直出(不走 Agent),降低整体延迟到 2.1s。
  • 对 Agent 的工具调用使用并发,节省 600ms。
  • 缓存高频订单状态,减少 API 调用。 最终:准确率 91%,满意度 4.3/5,成本较 V3 下降 30%。

7. 总结与未来展望

大模型应用开发正在从“文本生成”走向“任务执行”。要构建真正可用的产品,我们必须:

  1. 拥抱确定性:用约束解码和后处理保证输出结构。
  2. 打磨 RAG:混合召回 + 重排序 + 上下文压缩,让模型吃到“真正的知识”。
  3. 设计 Agent 框架:规划、工具、记忆三位一体,并支持并发和回退。
  4. 精打细算:模型路由、KV 缓存、推测解码,一个都不能少。
  5. 构建评估闭环:没有评估就没有优化,LLM-as-Judge 是当前最实用的方案。

未来的大模型应用将更加“无感知”——模型自主决策何时调用工具、如何规划步骤,甚至能够自我反思纠错。作为开发者,我们既要拥抱模型的进化,也要在工程层面扎好篱笆,确保每一次生成都可控、可追溯、可优化。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 大模型应用开发:从 Prompt 工程到 Agent 系统的架构跃迁
    • 1. 确定性输出:让 JSON 稳定得像传统接口
      • 1.1 结构化生成的三重保障
    • 2. RAG 进阶:从“向量检索”到“混合搜索 + 重排序”
      • 2.1 多路召回:BM25 + 向量 + 关键词
      • 2.2 重排序(Rerank)—— 用交叉编码器做最终筛选
      • 2.3 上下文窗口管理:防止“中间丢失”
    • 3. Agent 系统:从“一问一答”到“任务闭环”
      • 3.1 工具定义与调用规范
      • 3.2 ReAct 循环:推理与行动交织
      • 3.3 长期记忆:向量数据库存储用户偏好
    • 4. 延迟与成本优化:让 LLM 应用“跑得动、用得起”
      • 4.1 提示词缓存(Prompt Caching)
      • 4.2 路由策略:大模型与小模型的动态选择
      • 4.3 流式输出与首字延迟优化
    • 5. 可观测性:给大模型应用装上“黑匣子”
      • 5.1 记录关键事件(LangSmith / 自建)
      • 5.2 评估与回归测试(LLM-as-Judge)
    • 6. 实战案例:智能客服 Agent 的迭代之路
    • 7. 总结与未来展望
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档