LLM成长笔记(七): AI 应用框架与编排

AI 应用框架与编排学习博客(通俗原理 + 详细注释 · AI应用强化版)

LangChain 和 LlamaIndex 是构建 AI 应用的两大主流框架。这篇博客从实际问题出发 ,用生活化类比 建立直觉,通过术语详解 深入概念本质,再用原理剖析可运行代码带你一步步理解。每个知识点都聚焦在"什么时候用、为什么这样设计、如何调优"。


一、LangChain / LlamaIndex 快速上手

1. 框架定位与选择

问题

市面上有 LangChain、LlamaIndex 等多个框架,它们各自主攻什么方向?我该学哪个?

生活化类比

  • LangChain 就像瑞士军刀------有各种各样的组件(链、工具、记忆、代理),能灵活组合搭建复杂工作流,但也可能被繁多的选择弄得眼花。
  • LlamaIndex 就像专业的图书馆系统------专注于把文档整理成索引,提供高效的检索接口,在 RAG 场景下开箱即用。

术语详解

  • LangChain :通用 LLM 应用框架,核心抽象是 Chain(将多个步骤串联)和 Agent(让 LLM 自主决策调用工具)。生态庞大,适合构建需要复杂编排的应用。
  • LlamaIndex :专注数据索引和检索的框架,核心抽象是 QueryEngineRetriever。在文档问答、知识库检索场景中,比 LangChain 更简洁。它也提供了 ReActAgent 等轻量 Agent 能力,但 Agent 生态和灵活性不如 LangChain 丰富,复杂编排场景仍推荐 LangChain。
  • 实际选型:两者不是互斥的,经常混用------用 LlamaIndex 构建索引和检索,用 LangChain 做流程编排和工具调用。
维度 LangChain LlamaIndex
核心场景 通用编排、Agent、工具调用 文档索引、检索、RAG
Agent 能力 丰富(ReAct、OpenAI、Plan-and-Execute 等) 轻量(ReActAgent)
学习曲线 较陡(概念多) 较平缓(聚焦检索)
文档问答 需要手动组装 开箱即用
复杂工作流 原生支持 较弱
常用组合 编排 + 工具 索引 + 检索

2. 初级:Chain 构建与 Prompt 模板

问题

一个 LLM 应用通常需要多步处理------先格式化提示词,再调用模型,最后解析输出。如何优雅地串联这些步骤?

生活化类比
Chain 就像工厂流水线:每个工位做一件事(格式化 → 推理 → 提取),物料从一个工位流向下一个,最终输出成品。

术语详解

  • Chain :将多个组件按顺序串联的执行单元。最简单的 LLMChain 包含一个 Prompt 模板和一个 LLM。
  • Prompt 模板 :用 {变量} 占位的提示词,调用时动态填入具体内容。
  • Parser:从 LLM 的文本输出中提取结构化数据。

原理

Chain 的核心思想是"声明式组装"------你定义好每一步做什么,框架负责按序执行。LLMChain 内部先调用 Prompt 模板的 format() 填充变量,再调用 LLM 生成,最后用 Parser 解析。更复杂的 Chain 还可以串联多个 LLM 调用或插入检索步骤。

演示用例:用 LangChain 构建一个翻译 Chain

python 复制代码
# pip install langchain langchain-openai
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser

# 1. 定义 LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.3)

# 2. 定义 Prompt 模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的{source_lang}到{target_lang}翻译助手。只返回翻译结果,不要解释。"),
    ("user", "{text}")
])

# 3. 组装 Chain(Prompt → LLM → Parser)
chain = prompt | llm | StrOutputParser()

# 4. 调用 Chain(单个输入)
result = chain.invoke({
    "source_lang": "中文",
    "target_lang": "英文",
    "text": "检索增强生成是让大模型回答新知识的核心架构"
})
print("翻译结果:", result)

# 5. 批量处理多个输入(chain.batch)
inputs = [
    {"source_lang": "中文", "target_lang": "英文", "text": "你好世界"},
    {"source_lang": "中文", "target_lang": "英文", "text": "机器学习很有趣"},
]
batch_results = chain.batch(inputs)
print("批量结果:", batch_results)

输出结果

复制代码
翻译结果: Retrieval-Augmented Generation is the core architecture for enabling large models to answer questions based on new knowledge.
批量结果: ['Hello World', 'Machine learning is very interesting']

演示用例:用 LlamaIndex 快速搭建文档问答

python 复制代码
# pip install llama-index llama-index-embeddings-openai
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# 1. 加载文档目录下的所有文件
documents = SimpleDirectoryReader("./docs").load_data()
print(f"加载文档数:{len(documents)}")

# 2. 构建索引(自动分块、Embedding、存储)
index = VectorStoreIndex.from_documents(documents)
print("索引构建完成")

# 3. 创建查询引擎
query_engine = index.as_query_engine(similarity_top_k=3)

# 4. 提问
response = query_engine.query("什么是 RAG?")
print("回答:", response)

输出结果

复制代码
加载文档数:5
索引构建完成
回答: RAG(检索增强生成)是一种结合信息检索与文本生成的技术架构...

AI 应用场景 :Chain 适合需要多步处理的场景(翻译、摘要、提取),LlamaIndex 适合文档问答。两者可混用:用 LlamaIndex 检索,用 LangChain 编排后续处理。batch() 方法用于批量处理,能显著提升吞吐量。


3. 中级:LCEL 表达式、自定义工具、回调与追踪

问题

简单 Chain 只能串行执行,如何实现更复杂的流程------比如并行调用多个模型、条件分支、流式输出?

生活化类比
LCEL 就像乐高积木的接口规范------所有积木(组件)都遵循统一的插拔标准,你可以把检索器、模型、解析器像积木一样任意拼接。

术语详解

  • LCEL(LangChain Expression Language) :用 | 管道符串联组件,声明式定义执行流程。
  • RunnableParallel:并行执行多个分支。
  • RunnableBranch:条件分支,根据输入动态选择执行路径。
  • 回调(Callback):在 Chain 执行过程中插入钩子函数,用于日志记录、成本追踪、调试。多个回调按注册顺序依次执行。
  • 追踪(Tracing):记录每次调用的完整链路(输入、输出、耗时、token 用量)。常用 LangSmith 等工具。追踪的核心价值是定位性能瓶颈------比如发现"检索耗时 2 秒但 LLM 调用只有 0.5 秒",就能针对性优化检索策略。

原理深入:Runnable 协议

LCEL 的核心是 Runnable 协议------所有组件(Prompt 模板、LLM、Parser、Retriever)都实现了统一的接口:

方法 用途 适用场景
invoke(input) 同步执行,返回完整结果 脚本、简单请求
ainvoke(input) 异步执行 Web 服务中不阻塞事件循环
stream(input) 同步流式,逐块产出 本地调试流式输出
astream(input) 异步流式 Web 服务中的流式接口
batch(inputs) 批量并行处理多个输入 离线批处理

管道符 | 实际调用 Runnable.__or__() 方法,把前一个组件的输出自动传给下一个组件。LangChain 内部会检查类型兼容性------如果一个组件输出的类型与下一个组件期望的输入类型不匹配,会在运行时抛出清晰的错误。

如果你想自定义一个 Runnable 组件 ,只需继承 Runnable 并实现 invoke() 方法(以及可选的 astream 等),就能无缝接入 LCEL 管道。

演示用例:LCEL 并行调用 + 条件分支

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnableParallel, RunnableBranch

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

# 并行分支:同时调用两个不同风格的翻译
british_prompt = ChatPromptTemplate.from_template("Translate to British English: {text}")
american_prompt = ChatPromptTemplate.from_template("Translate to American English: {text}")

parallel_chain = RunnableParallel(
    british=british_prompt | llm | StrOutputParser(),
    american=american_prompt | llm | StrOutputParser()
)

text = "今天天气真好,适合出去玩"
results = parallel_chain.invoke({"text": text})
print("英式翻译:", results["british"])
print("美式翻译:", results["american"])

# 条件分支:根据文本长度选择模型
long_text_prompt = ChatPromptTemplate.from_template("Summarize: {text}")
short_text_prompt = ChatPromptTemplate.from_template("Translate to English: {text}")

branch_chain = RunnableBranch(
    (lambda x: len(x["text"]) > 100, long_text_prompt | llm | StrOutputParser()),
    short_text_prompt | llm | StrOutputParser()  # 默认分支
)
print("条件分支结果:", branch_chain.invoke({"text": "你好世界"}))

输出结果

复制代码
英式翻译: The weather is lovely today, perfect for going out.
美式翻译: The weather is really nice today, perfect for going out.
条件分支结果: Hello World

演示用例:回调记录 token 用量

python 复制代码
from langchain.callbacks import BaseCallbackHandler

class CostTracker(BaseCallbackHandler):
    """自定义回调:记录每次 LLM 调用的 token 消耗"""
    total_tokens = 0
    
    def on_llm_end(self, response, **kwargs):
        tokens = response.llm_output.get("token_usage", {}).get("total_tokens", 0)
        self.total_tokens += tokens
        print(f"本次消耗 {tokens} tokens,累计 {self.total_tokens} tokens")

tracker = CostTracker()
llm_with_callback = ChatOpenAI(model="gpt-3.5-turbo", callbacks=[tracker])

# 使用带回调的 LLM
result = llm_with_callback.invoke("写一句鼓励的话")
print("回复:", result.content)

输出结果

复制代码
本次消耗 42 tokens,累计 42 tokens
回复: 每一步的前进都是成长,坚持努力,你会遇见更好的自己。

AI 应用场景:LCEL 并行调用适合对比多个模型输出、批量处理;回调用于成本监控和调试;追踪是生产环境的必备能力,便于定位链路上的性能瓶颈(如发现检索步骤耗时异常,可针对性优化)。


4. 中级:流式处理

问题

大模型生成内容可能需要数秒甚至更久,如果等全部生成完再返回,用户会感觉卡顿。如何实现"边生成边输出"的流式体验?

生活化类比
流式处理就像打字机------敲一个字母出一个字母,而不是等整篇文章写完再给你看。用户能实时看到生成进度,体验流畅。

术语详解

  • stream(同步流)chain.stream(input) 返回同步迭代器,适合本地脚本。
  • astream(异步流)chain.astream(input) 返回异步迭代器,适合 Web 服务中不阻塞事件循环。
  • SSE 流式接口 :在 Web 服务中,用 StreamingResponse 将流式生成的事件推送给前端。

原理

LLM 的流式模式在底层返回一个迭代器,每生成一个 token 就 yield 出来。LangChain 的 stream() 封装了这个迭代器。在 Web 服务中,配合 StreamingResponse 和 SSE 协议,把每个 chunk 包装成 data: {chunk}\n\n 推送给前端。生产环境需处理生成中途出错的情况------通过 try/except 捕获异常,向客户端发送 data: [ERROR]\n\n 事件,避免连接挂起。

演示用例:LangChain 流式输出

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser

llm = ChatOpenAI(model="gpt-3.5-turbo", streaming=True)  # 开启流式
prompt = ChatPromptTemplate.from_template("用100字介绍{topic}")
chain = prompt | llm | StrOutputParser()

print("流式输出:")
for chunk in chain.stream({"topic": "检索增强生成"}):
    print(chunk, end="", flush=True)  # flush=True 立即显示
print()

输出结果(逐字出现)

复制代码
流式输出:
检索增强生成(RAG)是一种结合信息检索与文本生成的技术架构...

在 FastAPI 中提供流式接口(含错误处理)

python 复制代码
from fastapi import FastAPI
from starlette.responses import StreamingResponse

app = FastAPI()

async def generate_stream(topic: str):
    """异步生成器,逐块产出文本,中途出错时发送错误事件"""
    try:
        async for chunk in chain.astream({"topic": topic}):
            yield f"data: {chunk}\n\n"
        yield "data: [DONE]\n\n"
    except Exception as e:
        # 发生错误时通知客户端,而非直接断开连接
        yield f"data: [ERROR] {str(e)}\n\n"

@app.get("/stream")
async def stream_endpoint(topic: str):
    return StreamingResponse(
        generate_stream(topic),
        media_type="text/event-stream"
    )

AI 应用场景 :流式处理是 ChatGPT 式对话体验的核心。stream() 用于本地脚本调试,astream() 用于 Web 服务。生产环境务必处理流式生成中途的异常,向客户端发送明确的错误事件。


二、工具集成与函数调用

1. 初级:让 LLM 调用外部 API、计算器、搜索

问题

LLM 被训练数据截止日期限制,无法获取实时信息(天气、股票、搜索)。如何给它"接上网线"?

生活化类比
工具调用就像给人配手机:人本身知识有限,但有了手机就能随时搜索、计算、查天气。LLM 也一样,配上工具就能突破自身局限。

术语详解

  • Function Calling / Tool Calling:LLM 根据用户问题,自动判断是否需要调用外部工具,并生成符合工具要求的参数。你执行函数后把结果返回,模型再生成最终回答。
  • 工具定义:描述工具的名称、功能、参数 Schema(JSON Schema 格式)。
  • 工具执行流程:用户提问 → LLM 判断需不需要调用工具 → 如果需要,返回工具名和参数 → 你执行工具 → 把结果发给 LLM → LLM 生成最终回复。

原理

LLM 本身不能联网,但 Function Calling 机制让它能"表示"调用意图。你在代码中检测 LLM 返回的 tool_calls,执行真正的函数(如调天气 API),然后把结果作为新消息发给 LLM,完成闭环。注意:LLM 不直接调用工具,它只生成参数,实际执行由你的代码完成。

演示用例:让 LLM 使用计算器

python 复制代码
from openai import OpenAI
import json
import math

client = OpenAI()

# 定义工具
tools = [{
    "type": "function",
    "function": {
        "name": "calculator",
        "description": "执行数学计算,支持加减乘除和平方根",
        "parameters": {
            "type": "object",
            "properties": {
                "expression": {"type": "string", "description": "数学表达式,如 'sqrt(16)+3*2'"},
                "operation": {"type": "string", "enum": ["add","subtract","multiply","divide","sqrt"]}
            },
            "required": ["expression"]
        }
    }
}]

def execute_calculator(expression: str) -> float:
    """执行数学表达式(⚠️ 仅演示,生产环境必须用沙箱)"""
    allowed_names = {"sqrt": math.sqrt, "abs": abs, "round": round}
    return eval(expression, {"__builtins__": {}}, allowed_names)

# 用户提问
response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "16的平方根加上3乘以2等于多少?"}],
    tools=tools,
    tool_choice="auto"
)

msg = response.choices[0].message
if msg.tool_calls:
    tool_call = msg.tool_calls[0]
    args = json.loads(tool_call.function.arguments)
    print("LLM 想调用:", tool_call.function.name, "参数:", args)
    
    # 执行工具
    result = execute_calculator(args["expression"])
    print("计算结果:", result)
    
    # 把结果返回给 LLM
    final_response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "user", "content": "16的平方根加上3乘以2等于多少?"},
            msg,  # LLM 的工具调用消息
            {"role": "tool", "tool_call_id": tool_call.id, "content": str(result)}
        ]
    )
    print("最终回答:", final_response.choices[0].message.content)

输出结果

复制代码
LLM 想调用: calculator 参数: {'expression': 'sqrt(16)+3*2'}
计算结果: 10.0
最终回答: 16的平方根是4,3乘以2等于6,相加得到10。

AI 应用场景:工具调用让 LLM 能查询实时数据、操作数据库、调用第三方 API。这是构建 AI Agent 的基础------模型不再只是"说",还能"做"。


2. 中级:工具描述设计、错误重试、并行调用、安全沙箱

问题

工具多了以后,模型可能选错工具、参数传错、调用失败,如何设计健壮的工具系统?


2.1 工具描述设计

核心原则:工具描述是模型"看懂"工具能力的唯一途径,需要清晰、具体、包含边界。

设计清单

要素 说明 坏例子 好例子
名称 动词+名词,见名知意 do_stuff search_weather
描述 一句话说明做什么 + 适用场景 "搜索" "搜索指定城市的实时天气,返回温度和湿度"
参数名 语义明确 q city_name
参数描述 说明格式和约束 "字符串" "城市中文名称,如'北京'或'上海'"
枚举值 限定可选范围 operation: string `operation: "add"

2.2 错误重试

问题:工具调用可能因为网络波动、API 限流、参数格式错误而失败。

关键区分

错误类型 示例 是否重试 原因
瞬时错误 网络超时、API 限流(429)、服务暂时不可用(503) ✅ 重试 短时间内可能恢复
永久错误 参数格式错、权限不足(403)、资源不存在(404) ❌ 不重试 重试多少次都一样
python 复制代码
import time
import requests

def call_tool_with_retry(tool_func, args, max_retries=3, backoff=2):
    """带指数退避的重试机制(仅重试瞬时错误)"""
    for attempt in range(max_retries):
        try:
            return tool_func(**args)
        except requests.Timeout as e:
            # 网络超时 → 可重试
            if attempt == max_retries - 1:
                return f"工具调用超时(已重试{max_retries}次)"
            wait_time = backoff ** attempt
            print(f"超时,{wait_time}秒后重试...")
            time.sleep(wait_time)
        except (ValueError, KeyError, TypeError) as e:
            # 参数错误 → 不可重试,直接返回错误
            return f"参数错误(不重试):{str(e)}"
        except Exception as e:
            # 未知错误 → 谨慎重试一次
            if attempt == max_retries - 1:
                return f"工具调用失败:{str(e)}"
            time.sleep(backoff ** attempt)

2.3 并行调用

问题:用户问"北京和上海的天气分别怎么样",模型需要调用两次天气工具,串行调用耗时长。

解决方案 :检测 LLM 返回的 tool_calls 是否包含多个工具调用,如果多个且相互独立(参数不依赖彼此结果),就并行执行。

python 复制代码
import asyncio

async def execute_tools_parallel(tool_calls, tool_map):
    """并行执行多个独立的工具调用"""
    async def execute_one(call):
        func = tool_map[call.function.name]
        args = json.loads(call.function.arguments)
        try:
            return await func(**args)
        except Exception as e:
            # return_exceptions=True 让失败不影响其他工具
            return {"error": str(e), "tool": call.function.name}
    
    tasks = [execute_one(call) for call in tool_calls]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    return results

适用场景 :多个独立的查询(同时查天气、股价)、多个独立的计算。return_exceptions=True 确保某个工具失败不会中断其他工具的调用------你可以将成功和失败的结果汇总后一起发给 LLM,让它根据部分成功的结果生成回答或重新决策。


2.4 安全沙箱

⚠️ 生产环境警告:本章节的 eval 示例仅用于本地调试理解原理。生产环境严禁使用 eval 执行任何用户相关或 LLM 生成的代码,必须使用 Docker 沙箱或专用沙箱服务(如 E2B、OpenAI Code Interpreter)。

问题:如果让 LLM 生成代码并执行(如计算器场景),恶意输入可能导致任意代码执行。

防御层级

层级 措施 作用
1 白名单限制导入 只允许 mathdatetime 等安全模块
2 禁用 __builtins__ 阻止 open()__import__() 等危险函数
3 超时机制 防止死循环耗尽资源
4 资源限制 限制内存、CPU 使用
5 Docker 沙箱 生产级方案:在隔离容器中执行

三、面试模拟题

1. 对比型:LangChain 和 LlamaIndex 分别适合什么场景?你会怎么选?

答案要点:LangChain 适合需要复杂编排的场景(多步 Chain、Agent、工具调用),LlamaIndex 专注文档索引和检索。LlamaIndex 也有轻量 Agent 能力,但复杂编排仍推荐 LangChain。实际常混用------LlamaIndex 做检索,LangChain 做编排。选择取决于你的核心需求是"编排"还是"检索"。


2. 原理型 :LCEL 管道符 | 的背后是什么机制?如果我想自定义一个 Runnable 组件,需要实现哪些方法?

答案要点 :LCEL 基于 Runnable 协议,所有组件实现统一的 invoke()/stream()/batch()/ainvoke() 等接口。| 调用 __or__() 方法,把前一个组件的输出自动传给下一个组件。自定义组件只需继承 Runnable 并实现 invoke() 方法,就能无缝接入管道。


3. 场景型:你的工具调用有时失败(网络超时),有时模型选错工具。你从哪些方面优化?

答案要点:工具描述清晰化(名称、参数说明、枚举值);重试机制中区分瞬时错误(网络超时可重试)和永久错误(参数格式错不重试);工具数量过多时考虑分组或让模型只调用最相关的工具;并行执行独立的多个调用以减少总耗时;某个工具失败不影响其他并行调用。


4. 原理型:Function Calling 中,LLM 真的"调用"了外部 API 吗?完整的闭环是怎样的?

答案要点:LLM 不直接调用工具。它只根据用户问题判断是否需要调用,并生成符合工具定义的参数。实际执行由你的代码完成,结果返回给 LLM 后,LLM 再生成最终回答。整个过程是:用户提问 → LLM 判断 → 返回工具调用请求 → 你执行 → 返回结果 → LLM 生成回复。


5. 场景型:你的 AI 应用需要让用户看到"逐字输出"的流式体验,但如果 LLM 中途出错怎么办?

答案要点 :LLM 开启 streaming=True,后端用 chain.astream() 异步逐块获取 token,通过 FastAPI 的 StreamingResponse + SSE 推送给前端。关键是在生成器中使用 try/except 包裹,捕获异常后向客户端发送 data: [ERROR] {原因}\n\n 事件,避免连接挂起或静默失败。正常结束发送 data: [DONE]\n\n


AI 应用场景速查表

知识点 核心用途 典型场景
LangChain Chain 多步串联 翻译→摘要→提取流水线
LlamaIndex 索引 文档检索 知识库问答
Runnable 协议 统一组件接口 自定义 LCEL 组件
LCEL 并行 对比多个模型 A/B 测试不同提示
回调追踪 成本监控、性能定位 生产环境调试优化
流式处理 实时输出体验 ChatGPT 式对话
Function Calling 让 LLM 操作外部世界 搜索、计算、API 调用
工具描述设计 提高调用准确率 多工具 Agent
重试机制 处理瞬时错误 网络不稳定的生产环境
并行调用 减少多工具总耗时 同时查天气和股价
安全沙箱 防代码注入 用户可输入计算式
相关推荐
不爱记笔记几秒前
音视频转笔记工具横评2026,通义听悟、Ai好记、NotebookLM 实测对比
人工智能·笔记·ai·音视频·飞书·obsidian
Highcharts.js10 分钟前
如何下载使用Highcharts Grid 开发web可编辑表格
前端·人工智能·grid 表格·web 表格·在线编辑表格·highcharts表格安装·表格开发工具
服装 AI 增长黑客23 分钟前
AI落地服装店:从流量焦虑到经营闭环,哪些能力真实可用?
人工智能
weiwin12326 分钟前
MAF 入门(7):在 MAF 中使用 RAG
人工智能
kels889930 分钟前
单连接动态增减订阅:股票行情API后端降负载实战方案
开发语言·笔记·python·信息可视化·金融
JuiceFS30 分钟前
GPFS、Alluxio、JuiceFS 怎么选?一文看懂架构与适用场景
人工智能·后端
爱吃程序猿的喵33 分钟前
LingBot-Map 复现与原理剖析:基于 Geometric Context Transformer 的流式 3D 重建
人工智能·python·深度学习·计算机视觉·3d·transformer
俊哥V41 分钟前
每日 AI 研究简报 · 2026-07-23
人工智能·ai
a11177643 分钟前
基于PyTorch的动物图像识别系统 开源
人工智能·pytorch·python
用户62960593247051 小时前
从“浏览器根本区分不了”到真正实现:刷新不退出,关闭标签页自动退出,前端判断浏览器关闭和刷新
前端