news 2026/10/7 4:20:46

LangChain4j实战:Java工程师的大模型应用开发指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain4j实战:Java工程师的大模型应用开发指南

先说个真实感受:在Java生态里做LLM应用,过去很长一段时间都处于“看得到吃不到”的状态。Python那边LangChain、LlamaIndex玩得飞起,各种Agent、RAG、Memory组件随手一拼就是一个智能应用,而Java工程师想接大模型,往往只能自己封装HTTP请求、自己维护对话状态、自己写解析逻辑……不是说不行,就是太原始、太碎了,维护成本很高。直到LangChain4j出现,这个局面才算真正被打破。

LangChain4j,简单说就是把LangChain那一套“用大模型构建应用”的抽象能力搬到了Java世界,专门解决Java工程师接入LLM时最头疼的工程化问题。它是一个开源框架,核心目标是给Java/Kotlin/Android开发者提供一套完整的LLM应用开发工具链:对话管理、结构化输出、工具调用、Memory记忆、RAG检索增强、多模型切换等等,都能通过统一API搞定。本文要讲的这套新手实战教程,我尽量不堆概念,全程用可运行的代码和真实踩坑经历来带你走一遍,适合已经会用Spring Boot、但对LangChain4j完全陌生的Java后端开发,也适合那些被Python版LangChain“劝退”、想留在Java体系内做AI应用的同学。

1. 为什么Java团队应该认真考虑LangChain4j

1.1 从一次“手搓OpenAI客户端”的教训说起

先说一个我自己的项目经历。之前给公司做内部知识库问答机器人,第一版图省事,直接用RestTemplate封装了OpenAI的Chat Completions接口。核心逻辑很简单:把用户问题拼进Prompt,加上历史消息数组,POST出去,解析返回的JSON。跑通demo只花了一个下午,心里还挺美。

结果上线的第一个月就出事了。第一,多轮对话的历史消息管理完全靠手动拼List,用户对话一长,Token直接爆掉,时不时要自己截断;第二,模型偶尔返回非法JSON,解析崩溃,系统直接抛异常;第三,想给模型加个“查数据库”的能力,得自己写函数调用的协议,复杂到想骂人;第四,后来要换国内某家大模型,发现人家的请求格式跟OpenAI不完全一致,又要改一层适配……到那个阶段我就明白了:LLM应用的复杂度不在“调API”,而在API之外的工程问题。

LangChain4j恰恰是把这些“API之外的事”都抽象好了。它内置了统一的消息协议、内存管理策略、函数调用注册机制、模型适配层,让我从“面向JSON编程”回到“面向业务编程”。这一点,是用过的Java开发者都能明显感知到的差异。

1.2 LangChain4j与纯手写、Spring AI的横向对比

很多Java工程师第一次接触这个领域时会纠结:有现成的HTTP客户端,为什么要用框架?Spring官方后来也推出了Spring AI,那跟LangChain4j又怎么选?我自己的看法是这样的(纯个人使用感受,供参考):

维度手写RestTemplate方案Spring AILangChain4j
学习曲线低,但后续维护高中等中等偏下
多模型适配几乎为零,全自己写部分支持支持主流大厂模型,接口统一
Memory管理手动维护消息列表基础支持内置多种Memory实现,可自定义
工具/函数调用手写协议,非常痛苦支持支持,注解驱动,非常爽
RAG生态自己拼向量库起步阶段相对更完整,有独立模块
Java体系贴合度完全手动非常高高
社区活跃度-官方背书独立社区,迭代快

说实话,Spring AI背靠Spring官方,未来肯定有优势,但至少到现在这个时间点,LangChain4j在功能的完整性和灵活度上更胜一筹,尤其是Memory和AiServices这套抽象,设计得很成熟,很多场景能让我少写几百行样板代码。而且它的模块化做得很好,不绑架你的技术栈,你可以只引入需要的包。

1.3 这套框架解决了哪些“真实痛点”

我记得第一次看LangChain4j文档时,脑子里最大的一个感受是:它把我之前手写代码时所有“不舒服”的地方都给填平了。举几个具体例子:

  • 消息管理的痛:ChatMemory模块把用户消息、AI消息、系统消息统一管理,自带滚动窗口策略,再也不用自己数Token来截断历史了。
  • 结构化输出的痛:用AiServices加@SystemMessage、@UserMessage注解,配合BeanOutputParser,能让模型直接返回一个类型安全的Java对象,而不是裸的JSON字符串。
  • 函数调用的痛:只需要给Service接口加一个方法,再标注@Tool注解,框架自动生成工具协议,模型需要查数据时就调用你的Java方法,跟RPC一样自然。
  • 多模型切换的痛:OpenAI、通义、文心、Ollama本地模型……换一个ChatLanguageModel实现类就行,业务代码几乎不用动。

这些痛点不是Demo级别的“花活”,而是生产项目里每天都要面对的。LangChain4j把这些问题提到框架层面解决,我觉得这才是它真正的价值所在。

2. 环境准备与第一个可运行的最小Demo

2.1 依赖引入与版本避坑

先说版本和依赖,这是新手最容易栽跟头的地方。LangChain4j目前对Java 8/11/17都有支持,但我的建议是直接用Java 17,因为很多高级特性和部分依赖(比如向量存储的客户端)对旧版本兼容性一般,没必要给自己找麻烦。

我用的是Maven,引入核心依赖如下:

<properties> <langchain4j.version>0.31.0</langchain4j.version> </properties> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- 这里以OpenAI为例,国内模型可换 langchain4j-dashscope 或 langchain4j-qwen 等 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>${langchain4j.version}</version> </dependency> </dependencies>

这里有个特别重要的坑:LangChain4j的版本迭代非常快,0.x阶段API变动频繁,我最早用0.24版本写的代码,升级到0.31时就有不少Breaking Change。很多网上教程用的是老版本,你照着写大概率编译不过。建议以你引入的实际版本的官方文档为准,不要把网上贴的旧代码直接复制。

2.2 从“你好,大模型”到流式对话

依赖准备好之后,我们来写第一个能跑的Demo。这一步的目标很简单:让模型回复我们,并且能处理多轮对话的上下文。

import dev.langchain4j.data.message.AiMessage; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.memory.chat.MessageWindowChatMemory; public class QuickStartDemo { public static void main(String[] args) { // 1. 创建模型(这里用OpenAI协议,API Key建议从环境变量读取) ChatLanguageModel model = OpenAiChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-4o-mini") .temperature(0.7) .timeout(Duration.ofSeconds(60)) .build(); // 2. 创建带窗口记忆的聊天内存,最多保留最近20条消息 MessageWindowChatMemory memory = MessageWindowChatMemory.builder() .maxMessages(20) .build(); // 3. 先扔一句系统提示词 memory.add(SystemMessage.from("你是一个资深的Java技术顾问,回答要简洁、准确。")); // 4. 模拟用户连续提问 String question1 = "Java 21的虚拟线程和平台线程有什么区别?"; memory.add(UserMessage.from(question1)); AiMessage answer1 = model.generate(memory.messages()); System.out.println("AI回复:" + answer1.text()); memory.add(answer1); // 关键:把AI回复也塞回记忆里 String question2 = "那它在Tomcat里能直接用吗?"; memory.add(UserMessage.from(question2)); AiMessage answer2 = model.generate(memory.messages()); System.out.println("AI回复:" + answer2.text()); } }

看到没有?全程我们都没有手动拼接JSON数组。memory.messages()返回的就是框架维护好的完整消息链,传给model.generate()即可。这点对于做过多轮对话的人来说,真的是解放。

2.3 我用这个Demo验证过的几个关键点

这个最简单的例子背后,其实涉及到好几个容易踩的细节,我得单独拿出来说:

  • 模型名称的选择:gpt-4o-mini性价比高,做Demo完全够用。如果你接的是国内模型,比如通义千问对应的qwen-plus,或者本地跑的Ollama模型如qwen2.5:7b,模型名的传法都不一样,别硬套。
  • 超时时间:timeout参数强烈建议设置。模型接口偶尔会“思考很久”,不设超时的话,你的接口调用方会先超时,你这边还在傻傻等待,最后抛出一堆晦涩的异常。
  • Memory的消息顺序:消息顺序必须严格按照“先系统、再用户、再助手、再用户”这种交替顺序。如果乱序,部分模型不会报错,但回答质量会明显下降,这是我自己实测过的。
  • Token限制:MessageWindowChatMemory只按条数管理,不按Token数管理,所以如果你的业务是长文档对话,最好自己扩展一个按Token裁剪的Memory实现。

跑通这个Demo,你已经算是“入了门”。后面要做的,就是把框架的能力真正用起来。

3. 理解LangChain4j的灵魂:AiServices与结构化输出

3.1 AiServices是什么,凭什么说它是灵魂

很多新手跑完上面的Demo,觉得“这跟普通的SDK封装有什么区别”?区别就在AiServices。这个类是整个LangChain4j的精华,它做的事情可以用一句话概括:把一个普通的Java接口,变成一个有大脑、会调用工具的智能代理。

举个例子。你有一个Service接口:

public interface CustomerSupportAgent { String answer(String userQuestion); }

普通的实现就是写一个类,answer方法里写业务逻辑。而用AiServices,你可以这样:

CustomerSupportAgent agent = AiServices.builder(CustomerSupportAgent.class) .chatLanguageModel(model) .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) .tools(new CustomerInfoTool()) // 注册工具,模型可以按需调用 .build(); String response = agent.answer("帮我查一下订单2024001的物流状态");

看到区别了吗?answer方法里没有任何业务代码,但当你调用它时,模型会“思考”需要哪些信息,如果发现需要查订单数据,就会自动调用你注册的CustomerInfoTool里的方法,拿到结果后组织语言回复你。这就是Agent的能力,而这一切在Java里通过接口和注解就完成了。

3.2 @SystemMessage、@UserMessage与参数绑定

AiServices的功能不止“自动调用工具”,它还支持非常灵活的消息模板。看这个例子:

public interface TranslationService { @SystemMessage("你是一名专业的技术文档翻译。请将用户提供的内容翻译成{{language}},保持术语准确。") String translate(@UserMessage String text, @V("language") String language); }

这里的{{language}}是模板占位符,@V("language")会把参数值填进去。@SystemMessage定义的是系统提示词,@UserMessage标注的是用户消息来源。这比手动拼Prompt优雅一万倍,尤其适合封装公司内部的“稳定业务逻辑”——把Prompt工程固化在接口上,调用方完全感知不到大模型的存在。

我来整理一下AiServices的几种典型用法,方便你对照自己的场景:

用法核心注解/配置典型场景
简单问答无注解,直接String入参对话机器人
结构化信息抽取返回值用泛型/Record从非结构化文本提取实体
带模板的问答@UserMessage + @V翻译、总结、格式化
工具调用@Tool标注方法查数据库、调第三方API
流式输出返回类型用Flux<String>打字机效果的流式回复

3.3 结构化输出:让模型返回Java对象

这一点我单独拿出来讲,因为它在实际开发中的价值怎么强调都不为过。没有框架的时候,让模型返回JSON,你得在Prompt里写“你必须返回JSON格式,不要包含其他文字”,然后祈祷模型听话,再做JSON反序列化,还要处理各种格式污染。

在LangChain4j里,你只需要定义好Java类型,然后让接口方法直接返回这个类型:

public record OrderInfo(String orderId, String customerName, String status, double amount) { } public interface OrderParser { OrderInfo parseOrder(@UserMessage String text); } // 调用 OrderParser parser = AiServices.builder(OrderParser.class) .chatLanguageModel(model) .build(); OrderInfo info = parser.parseOrder("订单号2024001,张三,已发货,金额299.00元"); System.out.println(info.status()); // 输出:已发货

框架会自动生成“请提取信息,输出Json格式”的Prompt,并把模型输出解析成OrderInfo对象。如果模型输出不合法,有些解析器还能自动纠错,重试一次。这个能力在做信息抽取、表单识别、客服工单结构化时非常实用,我后来在好几个项目里都靠它省掉了大量正则解析代码。

4. 实战:做一个带“多路召回”的知识库问答系统

4.1 RAG整体架构与技术选型

聊完了基础,我们进入一个真正有点复杂度的实战项目:知识库问答系统。这是目前LLM应用落地最广泛的方向,热搜词里的“多路召回”就是这类系统的常见优化手段。

我先把RAG的经典流程搭起来:文档加载 -> 文本切分 -> 向量化入库 -> 用户问题向量化 -> 相似度检索 -> 拼接Prompt喂给LLM -> 生成回答。LangChain4j对这一整套流程都有对应的模块支持。

我的技术选型如下:

  • 嵌入模型(Embedding):用langchain4j-open-ai模块里自带的OpenAiEmbeddingModel,模型名text-embedding-3-small
  • 向量存储:本地开发用InMemoryEmbeddingStore(不依赖外部服务,重启丢失),生产环境建议换成PgVectorEmbeddingStore或Qdrant、Milvus等
  • 文档解析:官方DocumentParser,支持TXT、Markdown、PDF
  • 文本切分:DocumentSplitter系列的RecursiveCharacterDocumentSplitter

4.2 文档切分与向量化入库的工程细节

先看代码。这一步最容易出问题的地方有三个:切分粒度、重叠窗口、元数据保留。

import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; import dev.langchain4j.data.document.parser.TextDocumentParser; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.InMemoryEmbeddingStore; import dev.langchain4j.model.embedding.EmbeddingModel; // 1. 加载文档 Document doc = FileSystemDocumentLoader.loadDocument( Path.of("/path/to/your/java-knowledge.md"), new TextDocumentParser() ); // 2. 切分:每段最多500字符,重叠100字符 DocumentSplitter splitter = DocumentSplitters.recursive(500, 100); List<TextSegment> segments = splitter.split(doc); // 3. 逐段向量化并入存储 EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("text-embedding-3-small") .build(); EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>(); for (TextSegment segment : segments) { Embedding embedding = embeddingModel.embed(segment.text()).content(); store.add(embedding, segment); }

关于切分参数,我的建议是:

  • 如果知识库内容以代码为主,切分粒度放到300字符左右,太长了会把不相关的代码逻辑揉到一起;
  • 如果是技术文档、说明手册,500~800字符比较合适,既保留上下文语义,又不会超过向量模型的单次输入限制;
  • 重叠窗口建议设置为切片长度的20%,这样能有效避免“一句话被拦腰截断,前半段和后半段语义割裂”的问题;
  • 生产环境一定要给segment保留元数据,比如来源文档名、章节号、页码,方便回答时引用出处。

4.3 多路召回:向量检索+关键词检索的融合策略

这里引入“多路召回”的概念。你在做题或做搜索系统时应该知道,单一检索方式容易漏召回。比如向量检索擅长语义相似,但用户如果提问包含某个精确实体ID、型号、人名,纯向量检索的效果往往不如关键词索引(因为embedding可能把精确词泛化了)。

我的做法是同时跑两路:

  • 第一路:向量相似度检索,取Top K;
  • 第二路:基于关键词/或基于简单的全文索引(比如Lucene或数据库的全文搜索),用BM25类似的打分机制取Top K;
  • 最后合并:去掉重复文档,按新鲜度或置信度重排序,取前几个片段进入Prompt。

简化版代码如下:

// 向量召回 List<EmbeddingMatch<TextSegment>> vectorMatches = store.findRelevant( questionEmbedding, 3); // 关键词召回(这里用最简单的contains过滤,生产可换成Lucene/ES) List<TextSegment> keywordMatches = segments.stream() .filter(seg -> seg.text().contains(keyword)) .limit(3) .toList(); // 融合:按优先级合并,去重 Map<String, TextSegment> merged = new LinkedHashMap<>(); for (EmbeddingMatch<TextSegment> match : vectorMatches) { merged.putIfAbsent(match.embedded().text(), match.embedded()); } for (TextSegment segment : keywordMatches) { merged.putIfAbsent(segment.text(), segment); } // 拼接上下文 List<TextSegment> finalContexts = new ArrayList<>(merged.values()); String context = finalContexts.stream() .map(TextSegment::text) .collect(Collectors.joining("\n---\n")); String prompt = """ 基于以下资料回答问题,如果资料中没有明确的答案,请直接回答“根据现有知识库无法回答”。 资料: %s 问题:%s """.formatted(context, question);

注意:多路召回不是越多越好,重点是“召回质量”和“上下文长度”的权衡。我实测过,3~4个片段(每个500字左右)基本够用,超过4个核心内容位置会靠后,模型对中间信息的关注度会下降,反而影响回答效果。

4.4 让回答能“引用出处”的小技巧

知识库问答有个致命问题:模型可能会胡说八道,明明资料里没有的知识,它为了“帮到你”硬编一个答案。我在生产项目里用了一个很有效的办法:强制模型在回答末尾附上参考片段编号。

做法是在Prompt里增加约束:

String prompt = """ 请严格依据“资料”内容回答。 如果参考答案中有多条内容,请在回答末尾标注引用的片段编号,格式如 [1][3]。 资料: [1] %s [2] %s [3] %s 问题:%s """.formatted(...);

这样模型回答时,天然会优先使用给出的资料内容,而且能追溯到来源。用户看到引用之后,对结果的信任度会高很多。这个技巧成本为零,但价值极高。

5. 常见报错与调优:从踩坑到稳定运行

5.1 与模型通信相关的报错清单

写到这里,我把自己在实战中遇到的报错和解决方案整理成一张表,每一个都是真实遇到并解决的:

报错现象根因解决方案
OpenAiHttpException: 401API Key无效或环境变量没读取到优先检查System.getenv是否拿到了值,不要硬编码在代码里
SocketTimeoutException模型接口响应慢,默认超时太短显式配置timeout(Duration.ofSeconds(60)),甚至更长
JsonMappingException模型返回了被Markdown代码块包裹的JSON用解析器的容错模式,或者Prompt明确“不要用markdown代码块包裹JSON”
TokenLimitExceeded输入的Prompt+历史消息超过模型上下文窗口检查Memory条数设置,减少召回片段数量,或换更长上下文的模型
ClassNotFoundException引入了某个模块的包,但没有对应依赖检查langchain4j-open-ai等模块的传递依赖,缺啥补啥
模型回答牛头不对马嘴消息历史顺序错乱、缺少SystemMessage用MessageWindowChatMemory管理,不要手动塞消息

5.2 上下文管理与Token成本控制的经验

做LLM应用,Token就是钱。尤其是在内网知识库这种“文档又长、调用又多”的场景,成本控制是必须考虑的事。我总结了几个省钱又实用的经验:

  • 优先做检索再做大模型推理,而不是把整个文档全塞进去。RAG的意义就在这:每次只把最相关的2-3个片段喂给模型,而不是把500页文档全部拼接。
  • 对话历史没必要无限保留。绝大多数业务场景,20条以上的历史消息对回答质量几乎没有帮助,该截断就截断。
  • Embedding模型的成本也要关注。如果知识库有海量文档,离线离线批量先算好向量,千万别每次问答都重复向量化全库文档。
  • 流式输出(Streaming)也是降本手段之一。用户看到前几个字出来之时,心理等待时间大大缩短,但实际消耗Token其实差不多。不过用户体验提升非常明显。

关于流式输出,LangChain4j支持得很到位,接口返回Flux<String>即可配合WebFlux做SseEmitter,这一块网上资料也不少,本文就不展开了。

5.3 生产化前的最后检查清单

最后,我给准备把LangChain4j项目推上生产的你一份检查清单,这些都是我自己踩过的坑总结出来的:

  1. API Key不要写死在代码里:用环境变量或配置中心管理,最好支持多Key轮询,防止单Key限额。
  2. 配置好重试与降级:模型服务偶尔抖动,用Resilience4j或简单重试机制兜底,避免核心链路直接挂掉。
  3. 敏感信息过滤:大模型API请求会经过外部服务,要确保知识库里没有未脱敏的客户隐私数据再送出去。
  4. 日志记录Prompt和响应:出问题时能回溯,建议只记摘要和Token数,不记录完整敏感内容。
  5. 多路召回要留日志:记录哪一路召回了哪些片段、最终选了哪几个,这是优化检索效果的基础数据。
  6. 灰度发布:Prompt调整对回答风格影响巨大,先对小比例流量生效,观察用户反馈再全量。

以上6条如果你全部做到了,至少能避免80%的线上故障。我个人第一次上线LangChain4j应用时,就是忽略了第2条重试机制,结果大模型服务一波动,用户侧直接看到报错页面,被骂惨了。后来加了重试和降级,稳如老狗。

LangChain4j这个框架还在快速迭代中,踩了一些坑之后我也习惯了——跟上它官方的Roadmap更新,看Release Notes,比收藏老教程靠谱得多。毕竟工具会变,但“用工程化方法把大模型能力嵌进业务”这件事的方向,不会变。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 4:20:27

打家劫舍动态规划详解:LeetCode 198最大不相邻子序列

1. 题目到底在问什么&#xff1a;读懂“不相邻”三个字先说结论&#xff1a;LeetCode Hot 100 里的第 198 题“打家劫舍”&#xff0c;是动态规划入门最经典的一道题。它表面上是一个入室盗窃的情景题&#xff0c;剥掉故事外壳之后&#xff0c;本质是一个“在数组中选数字&…

作者头像 李华
网站建设 2026/10/7 4:20:16

微信小程序+Vue3+Spring Boot大创项目管理系统全流程实战

做毕设最怕的就是拿到了“只有登录界面和几个空页面”的假源码&#xff0c;看起来功能一堆&#xff0c;一跑全是死路。我这次把一套基于微信小程序的大学生创新创业训练项目管理系统完整跑通了全流程&#xff1a;学生打开小程序申报项目、上传材料、查进度&#xff0c;指导老师…

作者头像 李华
网站建设 2026/10/7 4:20:16

功放削顶失真与削底失真:成因诊断及电源余量优化实践

功放削顶失真这个词&#xff0c;玩音响的人多半都见过&#xff0c;但能把它彻底讲明白的人不多。前阵子帮朋友调试一台自己装的甲乙类推挽功放&#xff0c;大音量下声音发毛、高音刺耳&#xff0c;示波器一挂上去&#xff0c;正负半周都被整整齐齐地削平了&#xff0c;像是用刀…

作者头像 李华
网站建设 2026/10/7 4:17:47

agent-skills 实战:用技能链让 AI 编程助手遵循 TDD 工程纪律

1. 从“agent-skills”说起&#xff1a;为什么它值得你花时间研究第一次看到agent-skills这个标题&#xff0c;很多人会以为它只是某个仓库里堆了一堆提示词模板。但真正用过 AI coding agents 的人会明白&#xff0c;它解决的是一个非常具体、非常痛的问题&#xff1a;同一个模…

作者头像 李华
网站建设 2026/10/7 4:17:00

50个汽车性能MATLAB/Simulink仿真模型库深度评测与实战指南

1. 这套模型库到底值不值&#xff1a;从50个汽车性能模型说起前阵子把一套汽车性能MATLAB仿真模型库从头到尾过了一遍&#xff0c;一共50个Simulink模型&#xff0c;配套的源码以.m参数脚本和.slx/.mdl模型文件为主。说实话&#xff0c;刚拿到手的时候我是有点怀疑的&#xff1…

作者头像 李华
网站建设 2026/10/7 4:16:59

Agent Skills实战:让AI智能体按需调用技能包高效干活

最近我在折腾agent-skills这个开源项目&#xff0c;先说结论&#xff1a;它解决的不是"模型会不会回答问题"&#xff0c;而是"模型能不能动手把事做完"。第一次看到仓库时我以为它只是又一套工具调用框架的封装&#xff0c;但真正跑通一个技能包之后&#…

作者头像 李华