跳转至

[LangChain / LangGraph / SpringAl / LangChain4J /

Llamalndex / AutoGen / CrewAI ÷FXJłk]

框架总览速查表

image.png

🔗 LangChain vs 🕸️ LangGraph:核心区别是什么?

▎一句话说清

LangChain 是“搭积木”的链式框架,LangGraph 是“画流程图”的有状态图框架。

前者解决“怎么把多个组件串起来”,后者解决“怎么让组件根据运行时状态循环、分支、暂停甚至回退”。

▎深度解析

  1. 执行模型不同

  2. LangChain 的 LCEL(LangChain Expression Language)构建的是有向无环图(DAG)。链一旦 invoke,数据就从头流到尾,中间无法动态循环或根据中间结果回到上游。

  3. LangGraph 基于状态图(State Graph),节点之间可以形成环,边上可以挂条件判断,甚至支持暂停等待人工确认后再恢复执行。这让 Agent 的“观察-思考-行动”循环天然可被建模。

  4. 状态管理能力

  5. LangChain 的 Chain 本质是无状态的,每次调用独立,需要记忆时得额外接 Memory 组件。

  6. LangGraph 把状态作为一等公民,每个节点都接收状态、返回状态更新,图自动管理状态的持久化和传递。这对于多步 Agent 或需要纠错的流程至关重要。

  7. 复杂度天花板

  8. 简单 RAG、单一工具调用、顺序流水线,LangChain 足够优雅。

  9. 多 Agent 协作、带人工审核的工作流、根据运行结果动态决定下一步(甚至重试),LangGraph 的图结构不会变成“意大利面条”,因为它本身就是可观察、可测试的图。

▎示例代码对比

LangChain:搭建一个简单的 poet chain(无循环)

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一位诗人,用古诗风格回应。"),
    ("user", "{input}")
])
model = ChatOpenAI()
chain = prompt | model | StrOutputParser()
print(chain.invoke({"input": "春天"}))

这条链只能顺序执行:填 prompt → 调模型 → 解析输出,无法根据输出内容决定是否重新生成。

LangGraph:一个带自循环的简易 Agent(可重试直到满意)

from langgraph.graph import StateGraph, END
from typing import TypedDict

class AgentState(TypedDict):
    query: str
    answer: str
    attempts: int

def generate_answer(state):
    # 模拟调用LLM
    state["answer"] = f"回答:{state['query']} (第{state['attempts']}次)"
    state["attempts"] += 1
    return state

def should_continue(state):
    # 如果尝试次数<2且答案中不含“满意”,则循环回生成节点
    if state["attempts"] < 2 and "满意" not in state["answer"]:
        return "generate"
    return END

graph = StateGraph(AgentState)
graph.add_node("generate", generate_answer)
graph.set_entry_point("generate")
graph.add_conditional_edges("generate", should_continue, {
    "generate": "generate",
    END: END
})
app = graph.compile()
print(app.invoke({"query": "今天天气", "attempts": 0}))

这里 should_continue 就是 LangGraph 的精髓——用代码控制工作流的循环与终止。LangChain 的 LCEL 做不到这一点。

🧠 面试这么说最加分 “LangChain 让我快速串联模型、检索、工具,适合单一方向的管道;而一旦需要 Agent 反复规划、自我修正,或设计多人协作流程,我就会选用 LangGraph,因为它的状态图原生支持循环和持久化,图结构本身就能当系统设计文档看。”


📦 LangChain vs 📑 LlamaIndex:定位有什么不同?

LangChain 是通用 LLM 应用框架,LlamaIndex 是数据索引与检索的“涡轮增压器”。

一个负责编排应用逻辑,一个负责把私有数据变成 LLM 能消化的高精度知识。

▎深度解析

  1. 解决的核心问题不同

  2. LangChain 的初心:LLM 如何与外部世界交互? 于是有了 Agents、Tools、Chains、Memory,它像一个万能插座,把模型、数据库、API 连起来。

  3. LlamaIndex 的初心:如何让 LLM 精准理解我的几百页文档? 于是它重兵投入数据连接器、索引结构(向量、树、关键词、图)、高级检索策略和查询引擎。

  4. 能力侧重点

  5. LangChain 长于多步推理和工具调用:让模型自主决定先去查数据库,再调计算器,最后总结。

  6. LlamaIndex 长于复杂数据场景下的深度检索:例如同时检索 PDF 和数据库,用递归检索+摘要的方式回答“总结上一季度各产品线的问题”,并在答案里附带上来源页码。

  7. 本质不是竞争关系

在生产项目中,两者经常组合使用:用 LlamaIndex 构建强悍的检索器(Retriever),把它封装成 LangChain 的一个 Tool,再交给 Agent 调度。这样数据引擎和业务编排各司其职。

▎示例代码对比

LlamaIndex:把文档变成可问答的知识库

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

documents = SimpleDirectoryReader("./policies").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("年假最多可以累积多少天?")
print(response)

它帮你自动搞定文档切分、向量化、检索、合成,但你很难直接让它在回答前先去调一个 HR 系统 API。

LangChain:用 Agent 调度检索工具

from langchain.agents import create_openai_functions_agent, AgentExecutor
# 可以把上面 LlamaIndex 的 query_engine 包装成一个 Tool
tools = [hr_policy_tool, payroll_api_tool]  # 混合检索与API
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)
agent_executor.invoke({"input": "我剩几天年假,请假会扣多少钱?"})

Agent 决定先去查政策文档,再根据查到的规则调薪酬 API 计算,这正是 LangChain 的编排能力。

🧠 面试这样说让面试官点头 “我会把 LangChain 看作应用的骨架和大脑,负责调度;LlamaIndex 则是消化系统,专门把原始数据转化成高纯度知识。单纯做企业知识库问答,直接上 LlamaIndex 更快;一旦需要多步、多工具的复杂业务,就必须引入 LangChain 来编排。”

📦 LangChain vs 📑 LlamaIndex:定位有什么不同?

LangChain 是通用 LLM 应用框架,LlamaIndex 是数据索引与检索的“涡轮增压器”。

一个负责编排应用逻辑,一个负责把私有数据变成 LLM 能消化的高精度知识。

▎深度解析

  1. 解决的核心问题不同

  2. LangChain 的初心:LLM 如何与外部世界交互? 于是有了 Agents、Tools、Chains、Memory,它像一个万能插座,把模型、数据库、API 连起来。

  3. LlamaIndex 的初心:如何让 LLM 精准理解我的几百页文档? 于是它重兵投入数据连接器、索引结构(向量、树、关键词、图)、高级检索策略和查询引擎。

  4. 能力侧重点

  5. LangChain 长于多步推理和工具调用:让模型自主决定先去查数据库,再调计算器,最后总结。

  6. LlamaIndex 长于复杂数据场景下的深度检索:例如同时检索 PDF 和数据库,用递归检索+摘要的方式回答“总结上一季度各产品线的问题”,并在答案里附带上来源页码。

  7. 本质不是竞争关系

在生产项目中,两者经常组合使用:用 LlamaIndex 构建强悍的检索器(Retriever),把它封装成 LangChain 的一个 Tool,再交给 Agent 调度。这样数据引擎和业务编排各司其职。

▎示例代码对比

LlamaIndex:把文档变成可问答的知识库

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

documents = SimpleDirectoryReader("./policies").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("年假最多可以累积多少天?")
print(response)

它帮你自动搞定文档切分、向量化、检索、合成,但你很难直接让它在回答前先去调一个 HR 系统 API。

LangChain:用 Agent 调度检索工具

from langchain.agents import create_openai_functions_agent, AgentExecutor
# 可以把上面 LlamaIndex 的 query_engine 包装成一个 Tool
tools = [hr_policy_tool, payroll_api_tool]  # 混合检索与API
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)
agent_executor.invoke({"input": "我剩几天年假,请假会扣多少钱?"})

Agent 决定先去查政策文档,再根据查到的规则调薪酬 API 计算,这正是 LangChain 的编排能力。

🧠 面试这样说让面试官点头 “我会把 LangChain 看作应用的骨架和大脑,负责调度;LlamaIndex 则是消化系统,专门把原始数据转化成高纯度知识。单纯做企业知识库问答,直接上 LlamaIndex 更快;一旦需要多步、多工具的复杂业务,就必须引入 LangChain 来编排。”


🌱 Spring AI vs ⚡ LangChain4j:定位差异及 Java 项目选型

Spring AI 是 Spring 生态的原生 AI 模块,LangChain4j 是 LangChain 理念的 Java 独立实现。 一个让你像用 JdbcTemplate 一样用 AI,一个让你在 Java 里“写 Python 版 LangChain 的代码风格”。

▎深度解析

  1. 设计哲学

  2. Spring AI:遵循 Spring 的可移植服务抽象,定义 ChatClientEmbeddingClientVectorStore 等接口,通过 starter 自动配置切换 Azure OpenAI、通义千问等后端。你改一行配置就能换模型,代码零改动。

  3. LangChain4j:忠于 LangChain 的组件化链式组装思想,提供 ChatLanguageModelEmbeddingModelToolChain 等,API 风格接近 Python 版本,内置更丰富的模型集成(如直接对接各种大厂 API),同时对非 Spring 环境友好。

  4. 集成深度与约束

  5. 如果你的项目是典型的 Spring Boot 应用,Spring AI 能天然与 @Service@RestController、配置中心、Micrometer 监控等融合,你甚至可以使用 @Retryable 注解处理限流。

  6. LangChain4j 有 Spring Boot Starter,但它的核心库不依赖 Spring。它提供了诸如 InMemoryEmbeddingStoreAzureOpenAiChatModel 等自家实现,需要你手动配置 Bean,反而给了你更多细颗粒度的控制权。

  7. 功能广度和社区

  8. LangChain4j 在 AI 组件方面跑得更快,比如较早支持了 OpenAI 的函数调用、嵌入模型本地缓存、对话记忆等多种开箱即用实现。

  9. Spring AI 背靠 Spring 团队,稳定性和生态整合是其王牌,但在小众模型的集成上可能慢半拍。

▎Java 项目如何选型?(决策路径)

考量维度 选 Spring AI 选 LangChain4j
项目技术栈 基于 Spring Boot 3.x +,大量使用 Spring 生态 非 Spring 项目,或需要极简依赖
AI 集成广度 主流模型(Azure, OpenAI, 通义等)够用 需要 Hugging Face, Ollama, 各种国内模型原生支持
团队技能 熟悉 Spring 模板方法模式,追求最小学习成本 有 Python LangChain 经验,希望移植概念到 Java
可移植性需求 强烈需求:一键切换模型提供商,代码不变 需要更细粒度的控制,不怕改配置文件或代码
长期维护 依赖 Spring 官方生命周期,稳定但更新偏保守 社区活跃,更新快,紧跟前沿

示例代码感受一下风格差异

Spring AI:极简配置 + 依赖注入

# application.yml
spring:
  ai:
    openai:
      api-key: ${OPENAI_KEY}
@RestController
public class ChatController {
    private final ChatClient chatClient;
    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    @GetMapping("/chat")
    public String chat(@RequestParam String input) {
        return chatClient.prompt().user(input).call().content();
    }
}

完全 Spring 风格,感觉就像在使用 RestTemplate

LangChain4j:显式构建,链式组合

OpenAiChatModel model = OpenAiChatModel.builder()
    .apiKey(System.getenv("OPENAI_KEY"))
    .modelName("gpt-4o")
    .build();

Assistant assistant = AiServices.builder(Assistant.class)
    .chatLanguageModel(model)
    .tools(new Calculator())
    .build();
String answer = assistant.chat("计算 123*456 是多少?");

这里定义了一个带工具接口的 AI 服务,代码主见度很高,也更接近 LangChain 的“组装”概念。

实战建议:

  • 新项目且是 Spring Boot,直接用 Spring AI 起步,快速将 AI 能力嵌入已有服务体系。

  • 如果发现所需模型或高级 RAG 策略 Spring AI 暂不支持,可以引入 LangChain4j 作为补充,两者并不冲突(例如在同一个 Service 里分别调用两个框架的向量存储)。

  • 非 Spring 或追求极致控制的项目,LangChain4j 是更轻量的选择。

🧠 面试加分表达 “我的选型逻辑是‘尊重技术栈,预留扩展点’。Spring AI 让 AI 能力在 Spring 体系内变得和数据库访问一样简单,而 LangChain4j 在模型和检索策略的丰富度上更胜一筹。我会根据项目对整合深度和前沿功能的需求来做决定,也会考虑二者共存的方案。”

🤝 AutoGen 和 CrewAI 的多 Agent 协作模式有什么区别?

这两个框架都旨在让多个 AI Agent 协作完成任务,但它们的协作哲学截然不同。用一句话概括:AutoGen 是“对话驱动的协作”,而 CrewAI 是“角色驱动的流水线”。

image.png

核心区别一览:

维度 AutoGen CrewAI
协作机制 多 Agent 对话(Two-Agent Chat / Group Chat) 任务分配与执行(Sequential / Hierarchical)
控制流 对话流:Manager 动态选择下一个发言人 任务流:Task 的 context 参数定义依赖
角色定义 通过 system_message 描述角色 通过 role、goal、backstory 定义丰富人设
工具绑定 代码执行器(UserProxyAgent)是内置一等公民 工具通过 LangChain BaseTool 绑定给 Agent
适用场景 需要“来回讨论”的开放式任务,特别是涉及代码生成/执行 结构清晰、步骤明确、角色分工固定的工作流
学习曲线 中等,需理解对话驱动机制和 initiate_chat 较低,角色和任务的概念直观,上手快

示例代码对比:让两个框架完成同一个任务——“分析 sales.csv 并生成报告”

AutoGen 方式(对话驱动):

from autogen import AssistantAgent, UserProxyAgent

assistant = AssistantAgent("分析师", llm_config=...,
    system_message="你是数据分析师,生成 Python 代码分析 sales.csv。")
user_proxy = UserProxyAgent("执行者", code_execution_config={"work_dir":"."})

user_proxy.initiate_chat(assistant, message="分析 sales.csv 并生成报告")
# 接下来它们自动对话:助手写代码 → 执行者运行 → 助手看结果修正 → 最终输出报告

CrewAI 方式(任务驱动):

from crewai import Agent, Task, Crew, Process

analyst = Agent(role="分析师", goal="分析销售数据", backstory="...", tools=[code_tool])
reporter = Agent(role="报告员", goal="生成分析报告", backstory="...")

task1 = Task(description="分析 sales.csv", expected_output="数据洞察列表", agent=analyst)
task2 = Task(description="根据洞察生成 Markdown 报告", expected_output="报告全文",
             agent=reporter, context=[task1])

crew = Crew(agents=[analyst, reporter], tasks=[task1, task2], process=Process.sequential)
result = crew.kickoff()

选型直觉:

如果你的任务需要 Agent 之间“来回讨论、试错、根据结果调整”,例如写代码 → 运行 → 看报错 → 改代码,选 AutoGen,它的对话机制天然支持这种闭环。

如果你的任务可以画成一张清晰的甘特图,每一步的输入输出都明确,选 CrewAI,它的任务依赖模型更易于管理和监控。


🛠️你们项目用的是什么 AI 框架?为什么选它?有什么踩坑经验?

(这是一个典型的面试反问,你需要展示出 “在约束下做决策” 的工程思维。我以一个通用的“企业内部知识库问答”项目为例来说明,你可以替换成自己的真实经验。)

项目背景:为某金融机构构建内部合规知识库问答系统,需要对接已有的微服务、严格的安全审计、以及对延迟和吞吐的高要求。

我们选了 Spring AI + LangGraph 的组合。

为什么选 Spring AI?

  • 团队技术栈:团队全员 Java/Spring 背景,Spring AI 让我们无需学习 Python 生态就能快速构建 AI 应用。引入成本极低。

  • 生态集成:需要无缝对接已有的 Spring Cloud(服务发现、配置中心)、Spring Security(OAuth2 鉴权)、Micrometer(指标监控)。Spring AI 把这些能力天然带入了 AI 调用链,而不是另起炉灶。

  • 生产级特性:Advisor 机制让我们轻松实现 RAG、限流、审计日志等横切关注点,所有 AI 调用都是 Spring 管理的 Bean,行为可预测、可观测。

为什么还需要 LangGraph?

  • Spring AI 解决的是“怎么调模型”,但我们的合规问答涉及多轮检索、结果校验、人工审批等复杂流程,不是一个简单的“查→答”链路。

  • 我们用 LangGraph 把这些流程建模为状态图:检索 → 校验 → 生成 → 人工复核 → 终稿。每一步的状态都可以被 Checkpoint 持久化,支持中断和恢复(比如复核人过了一天才批准)。

  • LangGraph 的 StateGraph 让我们能够精确控制“校验不通过就回到检索”这种循环逻辑,而不仅仅是线性执行。

踩坑经验:三个真实教训

  1. RAG 检索结果的“看似相关实则无用”问题 直接用向量相似度检索回来的文档片段,经常不是回答问题真正需要的那段。例如用户问“合同违约金的法定上限是多少”,检索回来的是“违约金条款的定义”。我们的解决方式是:先让 LLM 改写用户问题为检索关键词(Query Rewriting),再检索;检索后又用一个轻量级 LLM 做相关性过滤,把不相关的片段直接在注入 Prompt 前剔除。这大幅提升了答案的准确率。

  2. 大模型延迟不可预测,需要完善的超时与降级 生产环境中 GPT-4 的响应时间从 2 秒到 30 秒不等。我们在 Spring AI 的 RestTemplate 层面设置了 connect timeout 和 read timeout,并用 Resilience4j 的 断路器 和 舱壁模式 隔离调用。当 OpenAI 不可用时,断路器打开,自动降级到本地的 Qwen 模型(通过 Ollama 部署)。降级后的回答质量虽有下降,但系统依然可用,而不是直接报错。

  3. 上下文窗口管理的“沉默成本” 多轮对话中,我们把对话历史全量塞进 Prompt,很快 token 消耗飞涨且模型开始“遗忘”早期指令。后来实现了 滑动窗口 + 摘要混合记忆:最近 3 轮保留原文,更早的历史由一个小模型生成 50 字的摘要。这在 Spring AI 中通过自定义 ConversationMemoryAdvisor 实现,token 成本降低了 60%,同时保持了对话连贯性。

一句话总结:选型不是选“最好的”框架,而是选“在当前团队技能、已有基础设施、任务复杂性三个约束下的最优解”。 我们选 Spring AI 是为了低摩擦上生产,选 LangGraph 是为了管好复杂流程,两者互补,缺一不可。


🔌 Semantic Kernel 是什么?和 LangChain 有什么区别?

Semantic Kernel (SK) 是微软推出的企业级 AI 编排框架,它的核心设计思想是“用你现有的代码来扩展 AI,而不是把 AI 作为中心”。

LangChain 哲学:AI 是大脑,一切围绕 AI 编排
  AI 调用 → 工具调用 → 链/图/Agent → 输出

Semantic Kernel 哲学:你的代码是骨架,AI 只是其中一个可调用的 Plugin
  你的业务逻辑 → 需要时调用 AI Plugin → AI 返回结果 → 继续你的逻辑

核心概念对比:

维度 LangChain Semantic Kernel
语言 Python / JavaScript C# / Python / Java
核心抽象 Chain, Agent, Tool, Memory Kernel, Plugin, Function, Planner
扩展方式 通过 LangChain 的 Tool 接口 通过 [KernelFunction] 特性,将任意方法变为 Plugin
编排模型 声明式管道 (LCEL)、图 (LangGraph) Planner 自动编排、Handlebars 模板
目标用户 AI 工程师、研究者 .NET 企业开发者
生态绑定 独立,模型无关 深度集成 Azure / Microsoft 365 / Copilot Stack
代表场景 通用 LLM 应用、RAG、Agent 企业应用智能化改造、Copilot 类产品

一个直观的例子:用 SK 创建一个“摘要 Plugin”

public class TextPlugin
{
    [KernelFunction, Description("将长文本总结为三句话")]
    public async Task<string> SummarizeAsync(
        [Description("要摘要的文本")] string text,
        Kernel kernel)
    {
        var prompt = $"请用三句话总结以下内容:\n{text}";
        return await kernel.InvokePromptAsync<string>(prompt);
    }
}

// 使用
var builder = Kernel.CreateBuilder();
builder.Plugins.AddFromType<TextPlugin>();
var kernel = builder.Build();

var summary = await kernel.InvokeAsync("TextPlugin", "Summarize",
    new() { ["text"] = longArticle });

关键区别:谁占主导地位

LangChain 的设计中,你的应用程序逻辑围绕 LLM 调用构建——你写一个 Chain,把 Prompt、LLM、Parser 串起来,工具只是这个 Chain 中的一个环节。而在 Semantic Kernel 中,你的应用程序逻辑是你原来的 C#/Java 代码,LLM 只是通过 Plugin 被调用的一个能力。这种“AI 作为依赖注入的一部分”的范式,让企业开发者感觉 LLM 像是一个新引入的“服务”,而不是重构整个应用的“中心”。

为什么选 Semantic Kernel?

  • 你的团队是 .NET 技术栈,不想为 AI 引入 Python 微服务。

  • 你需要深度集成 Microsoft 生态(Azure AI Search, Microsoft 365, Copilot)。

  • 你的应用已经有成熟的业务逻辑,只需要在关键节点嵌入 AI 能力(如自动分类、生成摘要、翻译),而不是从零构建 AI 原生的系统。

为什么选 LangChain?

  • 你需要构建复杂的 AI 原生工作流(多步推理、Agent 自主决策、复杂的 RAG 管道)。

  • 你需要在模型、向量库、工具之间灵活切换,且希望社区有最丰富的集成库。

  • 你的团队熟悉 Python 生态,且愿意用前沿框架。

两者可以共存: 在一些大型企业中,后端微服务用 Semantic Kernel 做轻量级 AI 嵌入(如邮件自动分类),而数据科学团队用 LangChain 构建复杂的分析 Agent,通过 REST/gRPC 互相调用。这不矛盾,只是在不同场景下选择了最顺手的工具。

📋团队新项目要做 RAG 问答系统,你会选哪个方案,理由是什么?

做这个决策之前,我会先看三个硬约束:

  • 团队的技术栈(Java 还是 Python?前端同学能碰后端吗?)

  • 项目的基础设施(已有 Spring Cloud 微服务?还是独立部署?)

  • 对稳定性、权限、审计的要求(金融/医疗等强监管行业?)

基于这三条,我把市面上的主流方案拉出来,做一个快速的权衡分析。

方案一:Spring AI + LangGraph (Java 生态)
  适合:团队以 Java/Spring 为主,已有微服务基础设施,对安全、审计、事务等有高要求。
  优势:与 Spring Security/Cloud/Micrometer 无缝集成,Advisor 机制天然适合做 RAG 切面。
         LangGraph 负责复杂流程(检索→校验→重试→审批),Spring AI 负责 AI 调用。
  劣势:相比 Python 生态,前沿特性(如最新的 Multi-Vector Retriever)落地稍慢。

方案二:LangChain + LangGraph (Python 生态)
  适合:团队 Python 能力强,希望快速迭代,愿意引入 Python 微服务。
  优势:社区最活跃,组件最丰富,调试工具(LangSmith)成熟,大量现成模板可复用。
  劣势:需要额外维护 Python 服务,与企业 Java 后端集成时要处理跨语言调用。

方案三:纯手工 + 向量库 SDK (如 Pinecone/Milvus)
  适合:需求极其简单,只有一个“查文档→回答”的直线链路,且团队不想引入框架。
  优势:无框架黑盒,完全掌控,包体小。
  劣势:当需求演进到多路召回、混合检索、上下文压缩时,你会发现自己在重写半个 LangChain。

方案四:LlamaIndex (Python)
  适合:项目以数据为中心,文档解析、索引结构是核心,RAG 链路重。
  优势:对索引结构(树索引、关键词索引、知识图谱索引)的支持很强。
  劣势:与 LangChain 功能重叠度高,社区略小,Agent 能力不如 LangChain。

我的推荐:对于大多数企业级 Java 项目,我会首选 Spring AI + LangGraph。

原因很直接:当公司已有的微服务体系、鉴权、配置中心都是 Spring 全家桶时,引入 Spring AI 的成本几乎为零——它就是一个 Starter,ChatModelVectorStore 都是 Spring Bean,你可以用 @Service@Transactional@Cacheable 等所有你熟悉的注解去构建 AI 逻辑。而 LangGraph 弥补了 Spring AI 在“复杂流程编排”上的短板,比如“检索结果太差→自动改写查询→二次检索→仍为空→转人工”这种带循环和分支的逻辑,用 LangGraph 的 StateGraph 表达比写一堆 if-else 清晰得多。

// 用 Spring AI 的 Advisor 实现 RAG 切面
@Component
public class RagAdvisor implements RequestResponseAdvisor {
    private final VectorStore vectorStore;

    @Override
    public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
        String query = request.userText();
        List<Document> docs = vectorStore.similaritySearch(SearchRequest.query(query).withTopK(4));
        String ctx = docs.stream().map(Document::getContent).collect(Collectors.joining("\n\n"));
        return AdvisedRequest.from(request)
                .withSystemText("基于以下参考资料回答:\n" + ctx + "\n\n" + request.systemText())
                .build();
    }
    // ...
}

🔍如何评估和选择适合自己项目的 LLM 模型?

选模型不是跑个分就完事了。我的做法是建立一个评估矩阵,从五个维度量化候选模型,然后根据业务权重打分。

五个评估维度:

维度 指标 评测方式
能力契合度 在真实业务场景下的准确率、召回率 构建业务专属测试集,人工+自动双重打分
推理性能 TTFT(首 token 延迟)、TPS(每秒生成 token)、端到端耗时 用 Locust/k6 压测,记录 P50/P95/P99
成本 每 1M token 价格、预估月消费 基于预估调用量计算,考虑 Input/Output 差异
上下文长度 最大上下文窗口,长文本下的检索准确率 Needle-in-a-Haystack 测试
合规与安全 数据是否出境、模型是否私有化部署、内容安全能力 法务+安全团队评估

评估流程(我的实际做法):

1. 候选池筛选
   根据合规要求(如数据不出境)和预算,从 GPT-4o、Claude 3.5 Sonnet、DeepSeek-V3、
   Qwen-Max、Llama 3.1 等中圈定 3~5 个候选。

2. 业务测试集构建
   从真实用户日志中抽取 200 条代表性 query,覆盖高频、长尾、多语言、多轮等场景。
   标注标准答案和关键信息点。

3. 离线评测
   编写自动化脚本,用每个候选模型回答这 200 条 query,收集结果。
   自动指标:ROUGE-L、BERTScore、答案长度、响应时间。
   人工打分:双盲评审,从准确性、完整性、流畅性三个角度 1-5 分。

4. 综合评分
   根据业务侧重赋权。比如客服场景“安全性”权重高,创意写作场景“多样性”权重高。
   最终选综合得分最高的模型,同时保留第二名作为降级备选。

示例代码:调用多个模型并记录结果

import time, json
from openai import OpenAI

models = ["gpt-4o", "claude-3-5-sonnet", "deepseek-chat"]
test_queries = [{"q": "合同违约金上限是多少?", "ref": "违约金不超过损失的30%"}]
results = []

for model_name in models:
    client = OpenAI(base_url=get_base_url(model_name), api_key=get_key(model_name))
    for item in test_queries:
        start = time.time()
        resp = client.chat.completions.create(
            model=model_name,
            messages=[{"role": "user", "content": item["q"]}],
            temperature=0
        )
        elapsed = time.time() - start
        results.append({
            "model": model_name,
            "query": item["q"],
            "answer": resp.choices[0].message.content,
            "latency": elapsed,
            "tokens": resp.usage.total_tokens
        })
# 后续可用另一个 LLM 或人工对 results 打分

选择决策树:

需要私有化部署? → 否 → 成本敏感? → 是 → 国产大模型 (DeepSeek/Qwen)
                  → 否 → 需要强推理? → 是 → GPT-4o / Claude 3.5
                         → 否 → GPT-4o-mini / Claude Haiku
需要私有化部署? → 是 → GPU 资源充足? → 是 → Llama 3.1 70B (微调)
                     → 否 → Qwen2.5 7B / DeepSeek-V2-Lite

模型选择不是一次性的,上线后我会持续收集用户反馈(点赞/点踩),每个月复盘一次模型效果,必要时切换或引入 A/B 测试。模型是易耗品,选择框架才是长期投资。


⚡如何在 SpringAI 中实现流式输出(Streaming)?

流式输出的本质是服务端不等待模型生成完整结果,而是通过长连接逐 token 推送。在 Spring AI 中,ChatModel 接口提供了 Flux<ChatResponse> stream(Prompt prompt) 方法,底层基于 Project Reactor,天然非阻塞。

实现步骤:

1. 配置 ChatModel(启用流式)
2. 在 Controller 中返回 Flux<ServerSentEvent>
3. 前端通过 EventSource 消费 SSE 流

第一步:确认模型支持流式 Spring AI 的 OpenAI Starter 默认支持流式,不需要额外配置。只需确保 ChatModel Bean 正常注入即可。

第二步:编写 Controller

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/api/chat")
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream(@RequestParam String q) {
        // 构建 Prompt
        Prompt prompt = new Prompt(new UserMessage(q));
        // 调用流式方法,返回 Flux<String>
        return chatClient.prompt()
                .user(q)
                .stream()
                .chatResponse()  // Flux<ChatResponse>
                .map(resp -> resp.getResult().getOutput().getContent());
    }
}

返回的 Flux<String> 会被 Spring WebFlux 自动序列化为 SSE 事件流。前端收到的原始数据格式为:

data: 今天
data: 天气
data: 很好
data: ...

第三步:前端消费(Vue 3 示例)

const eventSource = new EventSource('/api/chat/stream?q=今天天气怎么样');
eventSource.onmessage = (event) => {
    // event.data 就是后端每次推送的文本片段
    document.getElementById('output').innerText += event.data;
};
eventSource.onerror = () => {
    eventSource.close();
};

第四步:处理流式输出中的 Function Calling 和错误 当模型在流式模式下决定调用工具时,Spring AI 会先返回一个包含 tool_callsChatResponse,你需要中断流,执行工具,然后把结果追加到历史中,重新发起流式调用。这通常需要在前端实现“等待-执行-再连”的逻辑。

// 如果需要处理流式中的 tool calls,通常用更底层的 ChatModel.stream()
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
return flux.flatMap(response -> {
    if (response.hasToolCalls()) {
        // 执行工具,用结果构建新消息,重新 stream
        return executeToolsAndRestream(response);
    }
    return Flux.just(response.getResult().getOutput().getContent());
});

第五步:性能与容错

  • 超时:在 application.yml 中设置 spring.ai.openai.chat.options.timeout 或使用 Flux.timeout(Duration.ofSeconds(30))

  • 背压:Reactor 默认处理背压,无需额外配置。

  • 取消:前端 EventSource.close() 会断开连接,后端 Flux 自动取消,底层 HTTP 连接释放。

配置文件示例:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o
          temperature: 0.7
          stream: true   # 可选,显式开启流式

收束: Spring AI 的流式输出把 Reactor 的响应式能力搬到了 AI 领域。你不用自己管理 SSE 协议、不用处理异步线程的切换,只需要定义好 Flux 管道,Spring 会帮你把 token 流稳稳地送到前端。这对于提升用户体验、降低首字延迟至关重要——在真实的 RAG 应用中,流式输出不是“加分项”,而是“必须项”。