LangChain4j声明式Agent:动态代理与函数回调机制深度解析

1. 为什么值得深挖 LangChain4j 的声明式 Agent

第一次看到 LangChain4j 的@Agent注解和AiServices那套写法时,我的反应是"这也太魔法了"。你只写一个接口,方法上标几个注解,框架就能自动帮你把大模型的函数调用、参数解析、结果回填全部串起来。表面上看是几行声明式代码,底下藏着的却是一整套动态代理加反射回调的机制。

这套东西解决的核心问题是:让 Java 开发者用最贴近原生语言习惯的方式,把大模型能力接进业务代码。传统做法里,你得手动拼 Prompt、手动解析模型返回的 JSON、手动判断该调哪个方法、手动把结果塞回去再发起第二轮请求。一个完整的工具调用链路写下来,光胶水代码就上百行,而且每接一个新工具就要复制一遍。声明式 Agent 把这套流程抽象成了接口定义,你只描述"我要什么",框架负责"怎么做到"。

适合读这篇的人有三类:一是已经在用 LangChain4j 但只会照着文档抄、想搞懂底层到底发生了什么的开发者;二是准备自研 Agent 框架、想借鉴动态代理加回调这套设计思路的架构师;三是被各种"AI 框架"概念绕晕、想找一个具体实现来打通认知的技术负责人。我会从架构设计讲到动态代理的实现细节,再讲到函数回调的完整链路,中间穿插大量实操代码和我自己踩过的坑。

需要提前说明的是,下面涉及的具体实现细节,一部分来自我对框架源码的阅读,一部分是基于常见动态代理和反射实践的合理推断。框架版本迭代很快,不同版本在细节上可能有出入,但核心思路是稳定的。

2. 声明式 Agent 的整体架构设计思路

2.1 从命令式到声明式的范式转变

要理解 LangChain4j 为什么这么设计,得先看命令式写法有多痛苦。假设你要做一个能查天气、能算数学、能查数据库的助手,命令式代码大概长这样:先定义三个工具的 JSON Schema 描述,塞进请求体;拿到模型返回后判断finish_reason是不是tool_calls;如果是,解析出函数名和参数;写一个巨大的 switch-case 分发到对应方法;执行完把结果包装成tool角色的消息追加到对话历史;再发起第二轮请求。这套流程每加一个工具就要改 switch,参数类型变了还要改解析逻辑。

声明式把这一切反转了。你定义一个接口:

interface Assistant { @SystemMessage("你是一个乐于助人的助手") String chat(String userMessage); @Tool("查询指定城市的天气") String getWeather(@P("城市名") String city); }

然后AiServices.create(Assistant.class, model)一行拿到代理对象,直接assistant.chat("北京天气怎么样"),框架自动完成"模型决定调 getWeather、传参北京、拿到结果、再让模型总结"的全过程。这个反转的本质是:把"怎么做"的编排逻辑从业务代码里抽走,交给框架的运行时。

2.2 三层职责的清晰切分

我读下来觉得这套架构最漂亮的地方是职责切分。它大致分成三层:

第一层是接口声明层,也就是你写的那个 interface。这一层只表达意图:有哪些能力、每个能力的语义是什么、参数叫什么。它不关心模型是谁、不关心怎么调用。

第二层是代理编排层,由AiServices生成的动态代理承担。它拦截接口方法调用,负责组装 Prompt、注入工具描述、发起模型请求、解析响应、判断是否需要工具调用、执行工具、回填结果、再次请求,直到拿到最终答案。

第三层是模型适配层,由ChatLanguageModel及其各种实现承担。它屏蔽了不同模型服务商的 API 差异,统一成"给一组消息、返回一条消息"的接口。

这种切分带来的好处是每一层都能独立替换。换模型只动第三层,加工具只动第一层,编排逻辑的调整集中在第二层。对比那些把三者揉在一起的框架,维护成本差距是数量级的。

2.3 为什么选动态代理而不是代码生成

这里有个关键选型问题:实现声明式接口,业界常见两条路——运行期动态代理,或者编译期代码生成(类似注解处理器生成实现类)。LangChain4j 选了前者。

动态代理的优势是零构建侵入。你不需要在 pom 里加注解处理器,不需要处理生成代码的目录,不需要担心 IDE 索引不到生成的类。AiServices.create一调用,代理对象当场就有了。对于快速迭代的 AI 应用场景,这个体验很重要,因为 Prompt 和工具定义经常改,编译期生成意味着每次改都要重新构建。

代价是运行期开销和调试难度。动态代理的方法调用比直接调用慢,反射解析注解也有成本。但实测下来,相对于一次大模型请求动辄几百毫秒到几秒的延迟,代理层的开销完全可以忽略。调试难度确实存在,栈里全是$Proxy和InvocationHandler,不过框架通过良好的日志弥补了这一点。

提示:如果你的场景对启动速度极度敏感,或者需要 GraalVM 原生镜像,动态代理会带来额外配置成本。这种情况下可以考虑自己写实现类,但会失去声明式的便利。

3. 动态代理层的核心实现细节

3.1 AiServices.create 背后发生了什么

AiServices.create(Assistant.class, model)这一行是整个魔法的入口。它内部做的事情,我拆成几个关键步骤:

首先,它收集接口上的所有元信息。包括接口级别的@SystemMessage、每个方法的@UserMessage、方法参数上的@V、@MemoryId、@Tool注解等。这些信息被解析成一个内部的上下文对象,我把它叫做"接口契约"。

然后,它构建一个InvocationHandler。这个 handler 持有接口契约、模型实例、以及可选的记忆存储、检索器、工具执行器等组件。所有对代理对象的方法调用都会进到这个 handler 的invoke方法。

最后,用Proxy.newProxyInstance生成代理对象返回。注意这里用的是 JDK 动态代理,所以你的接口必须是 interface,不能是抽象类。这也是为什么 LangChain4j 的 Agent 都定义成接口。

3.2 invoke 方法的完整处理链路

invoke方法是整个代理层的心脏。它大致按这个顺序处理一次调用:

  1. 识别方法类型。如果是Object的toString、hashCode、equals,直接走默认实现,不触发模型调用。这个判断很关键,否则日志打印、集合操作都会意外触发大模型请求,烧钱又慢。

  2. 解析方法参数。根据参数上的注解,把实参映射到 Prompt 模板里的占位符。比如@V("city")标注的参数会替换模板里的{{city}}。没有注解的参数按位置或名称匹配。

  3. 组装消息列表。把系统消息、历史记忆、当前用户消息按顺序拼成List<ChatMessage>。如果配置了检索增强,还会在这里插入检索到的上下文。

  4. 注入工具定义。扫描接口里所有@Tool方法,把它们的名称、描述、参数 Schema 转成模型能理解的工具描述格式,附加到请求里。

  5. 发起模型调用。调用model.generate(messages, toolSpecifications)。

  6. 处理响应。如果模型返回的是普通文本,直接返回。如果返回的是工具调用请求,进入工具执行循环。

  7. 工具执行循环。对每个工具调用,找到对应的接口方法,用反射调用它,拿到结果,包装成ToolExecutionResultMessage追加到消息列表,然后回到第 5 步再次请求模型。这个循环会一直持续到模型不再请求工具、直接给出文本答案为止。

  8. 结果转换。把模型返回的文本转换成接口方法声明的返回类型。如果返回类型是String就直接给,如果是 POJO 就尝试反序列化,如果是Result<T>就包装上 token 用量等元信息。

3.3 参数解析的细节与坑

参数解析这块看着简单,实际坑不少。我举几个真实遇到的场景。

第一个坑是参数名丢失。Java 编译后默认不保留方法参数名,所以@P("city")这种显式命名是必须的。如果你偷懒不写,框架可能拿到的是arg0、arg1,模型根本不知道这是什么。我建议所有工具方法的参数都显式标注名称和描述,描述越清楚,模型选对工具、传对参数的概率越高。

第二个坑是复杂参数类型。工具方法的参数如果是自定义对象,框架需要把它转成 JSON Schema 描述给模型。这时候对象里的字段名、类型、是否必填都会影响模型的理解。我一般会给这类对象加详细的字段注释,或者干脆拆成多个简单参数,降低模型的理解负担。

第三个坑是可选参数。有些工具参数是可选的,但模型不一定知道。你需要在描述里明确写"可选,默认值是 X",否则模型可能每次都硬编一个值,或者干脆不传导致调用失败。

3.4 记忆与上下文的注入时机

记忆管理是 Agent 能不能多轮对话的关键。LangChain4j 里通过ChatMemory接口抽象,常见实现是滑动窗口式的消息列表。代理层在组装消息时,会把记忆里的历史消息插在系统消息之后、当前用户消息之前。

这里有个容易忽略的细节:工具调用的中间消息要不要进记忆。一次带工具调用的对话,消息序列里会有AiMessage(含工具调用请求)和ToolExecutionResultMessage。如果这些也存进记忆,下一轮对话时模型会看到完整的工具调用历史,有助于理解上下文,但会快速消耗 token。我的做法是只保留最终的UserMessage和AiMessage,中间的调用细节丢弃,除非业务确实需要追溯。

注意:记忆的窗口大小要结合模型上下文长度和业务复杂度调。窗口太小会丢上下文,太大会爆 token 且拖慢响应。我一般从 10 轮开始试,根据实际效果调整。

4. 大模型函数回调的底层机制

4.1 函数调用是怎么被模型"理解"的

很多人以为模型真的"调用"了你的函数,其实不是。模型做的事情只有一件:生成一段结构化的文本,表示它想调用某个函数、传什么参数。真正执行函数的是你的代码。

具体来说,你在请求里附带了工具描述(函数名、用途、参数 Schema)。模型在生成回复时,如果判断需要调用工具,就会输出一个特殊格式的响应,里面包含函数名和参数 JSON。这个响应被框架解析后,才触发实际的 Java 方法调用。

理解这一点很重要,因为它解释了为什么工具描述的质量直接决定 Agent 的可靠性。模型看不到你的代码实现,它只能根据你给的文字描述来判断"这个工具是干什么的、什么时候该用、参数怎么填"。描述写得含糊,模型就会选错工具或传错参数。

4.2 工具描述的 Schema 生成

框架怎么把 Java 方法转成模型能理解的工具描述?核心是反射加 JSON Schema 生成。

对于方法String getWeather(@P("城市名") String city),框架会生成类似这样的描述:

{ "name": "getWeather", "description": "查询指定城市的天气", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名" } }, "required": ["city"] } }

这个 Schema 会随请求一起发给模型。不同模型服务商对工具描述的格式要求略有差异,但核心字段都是名称、描述、参数定义。框架的模型适配层负责把统一格式转成各家要求的格式。

4.3 工具执行循环的终止条件

工具执行循环什么时候停?答案是:当模型返回的响应里不再包含工具调用请求时。也就是说,模型自己决定"我不需要再调工具了,可以直接回答用户"。

这个设计有个隐患:如果模型陷入"反复调用同一个工具"的循环怎么办?比如它一直觉得信息不够,反复查同一个接口。实际中确实会遇到。防护手段有几个:一是设置最大迭代次数,超过就强制返回;二是在工具描述里写清楚"这个工具返回的信息已经足够,不要重复调用";三是在系统消息里强调"如果已有足够信息,直接回答"。

我踩过一次坑:一个查询订单状态的工具,模型因为返回结果里有个字段它不理解,反复调用想搞清楚,结果循环了七八次。后来我在工具返回值里加了明确的字段说明,问题就解决了。所以工具返回值的可读性和工具描述一样重要。

4.4 多工具并行的处理

有些模型支持一次返回多个工具调用请求,也就是并行调用。框架需要处理这种情况:依次或并发执行多个工具,把所有结果都追加到消息列表,再统一发起下一轮请求。

这里要注意工具之间的依赖关系。如果工具 B 的参数依赖工具 A 的结果,那它们就不能并行。但模型不一定知道这个依赖,它可能一次性把两个都请求了。我的经验是,有依赖关系的工具,要么合并成一个工具,要么在描述里明确写"调用本工具前必须先调用 X 工具获取 Y 参数"。后者依赖模型的遵循能力,不是百分百可靠,所以能合并就合并。

5. 完整实操:从零搭一个声明式 Agent

5.1 环境准备与依赖引入

先明确技术栈。我用的是 Maven 项目,JDK 17,LangChain4j 的核心包加一个模型适配包。依赖大致是:

<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.35.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>0.35.0</version> </dependency>

版本号要统一,混用不同版本容易出现 API 不兼容。我建议用 BOM 管理版本,避免手动对齐的麻烦。

5.2 定义模型实例

模型实例是 Agent 的动力源。以 OpenAI 兼容接口为例:

ChatLanguageModel model = OpenAiChatModel.builder() .baseUrl("你的服务地址") .apiKey("你的密钥") .modelName("你的模型名") .temperature(0.7) .timeout(Duration.ofSeconds(60)) .build();

几个参数的经验值:temperature做工具调用时建议调低到 0.2 到 0.5,太高会让模型"发挥创意",选错工具;timeout要设够,带工具调用的多轮请求总耗时可能很长,我一般设 60 秒起步;如果服务商支持,开启流式响应能显著改善用户体验。

5.3 定义工具接口

工具接口是整个 Agent 的能力边界。我以一个"个人助理"为例:

interface PersonalAssistant { @SystemMessage("你是一个个人助理,可以帮用户查天气、记待办、算数。回答简洁友好。") String chat(@MemoryId String sessionId, @UserMessage String message); @Tool("查询指定城市的当前天气") String getWeather(@P("城市名称,如北京") String city); @Tool("添加一条待办事项") String addTodo(@P("待办内容") String content, @P("优先级,可选值:高/中/低") String priority); @Tool("计算数学表达式") double calculate(@P("数学表达式,如 1+2*3") String expression); }

注意chat方法上的@MemoryId,它让不同会话拥有独立的记忆。@UserMessage标注哪个参数是用户输入。工具方法上的@Tool描述要写得像给同事交代任务一样清楚。

5.4 组装并创建 Agent

ChatMemory memory = MessageWindowChatMemory.withMaxMessages(20); PersonalAssistant assistant = AiServices.builder(PersonalAssistant.class) .chatLanguageModel(model) .chatMemoryProvider(sessionId -> MessageWindowChatMemory.builder() .id(sessionId) .maxMessages(20) .build()) .tools(new TodoTools()) // 如果工具有独立实现类 .build();

这里chatMemoryProvider按会话 ID 提供独立记忆,是多用户场景的标准做法。如果工具方法直接定义在接口里,框架会通过代理调用;如果工具有独立实现类,用.tools()注册。

5.5 运行与观察

String reply = assistant.chat("user-001", "北京今天天气怎么样?"); System.out.println(reply);

运行后,框架内部会经历:组装消息、注入工具、请求模型、模型返回"调用 getWeather(city=北京)"、执行方法、回填结果、再次请求、模型生成最终回答。整个过程对调用方完全透明。

我强烈建议第一次跑的时候打开 DEBUG 日志,把每一轮的消息列表打出来看。你会直观看到消息是怎么一步步累积的,工具调用请求长什么样,结果怎么回填。这个观察过程比读十遍文档都管用。

6. 常见问题与排查技巧实录

6.1 模型不调用工具怎么办

这是最高频的问题。模型明明该调工具,却直接编了个答案。排查顺序:

先看工具描述是否清楚。描述太笼统,模型不知道什么时候该用。把"查询天气"改成"查询指定城市的当前实时天气,当用户询问天气时使用",命中率会明显提升。

再看系统消息有没有引导。在系统消息里加一句"需要实时信息时,优先使用提供的工具,不要凭记忆回答",能显著改善。

最后看模型能力。有些小模型对函数调用的支持很弱,换一个函数调用能力强的模型往往立竿见影。

6.2 参数传错或漏传

模型把参数名搞错、类型搞错、或者该传的没传。对策是在参数描述里给例子。@P("城市名称,如北京、上海")比@P("城市")好得多。对于枚举型参数,把所有可选值列出来。对于必填参数,确保 Schema 里标记为 required。

6.3 工具执行抛异常

工具方法内部报错,整个 Agent 调用就崩了。我的做法是在工具方法里做防御性编程,把异常转成友好的错误信息返回,而不是抛出去。比如查询失败返回"查询服务暂时不可用,请稍后重试",模型拿到这个信息后可以决定是重试还是告知用户,而不是整个链路中断。

6.4 排查速查表

现象可能原因排查方向
模型不调工具描述不清/模型能力弱优化描述、换模型
参数错误描述缺例子/类型模糊补充示例、明确类型
循环调用返回值不清晰优化返回值可读性
响应超时工具慢/轮次多加超时、限制迭代次数
记忆混乱会话 ID 未隔离检查 MemoryId 配置
代理报错接口非 interface改为接口定义

6.5 几个独家避坑心得

第一,工具粒度要适中。太细会导致模型要调很多次,太粗会导致参数复杂模型填不对。我一般按"一个完整的业务动作"来切分。

第二,给工具起名要有语义。getWeather比gw好,searchOrderByUser比query1好。模型靠名字和描述双重判断,名字本身就是信息。

第三,日志一定要打全。把每轮请求的消息列表、模型原始响应、工具调用参数和结果都记下来。出问题时这些日志是唯一的线索。我吃过没打日志的亏,线上出问题只能靠猜。

第四,给 Agent 加兜底。模型可能返回空、返回乱码、或者工具全失败。在业务层包一层,异常时给用户一个体面的回复,而不是把栈信息暴露出去。

7. 这套架构的边界与扩展方向

声明式 Agent 不是银弹。它的强项是工具编排清晰、代码量少、易维护,弱项是复杂控制流表达困难。如果你的业务需要"根据工具 A 的结果决定走分支 B 还是 C,分支里再嵌套工具调用",纯声明式会很别扭。这种场景我一般混合使用:声明式处理标准工具调用,复杂编排用命令式代码显式控制。

扩展方向上,我比较看好几个点。一是工具的动态注册,运行期根据配置决定暴露哪些工具,适合多租户场景。二是工具调用的可观测性,把每次调用的耗时、成功率、参数分布采集起来,用于优化工具描述。三是多 Agent 协作,让一个 Agent 把子任务委托给另一个 Agent,这在 LangChain4j 里可以通过把 Agent 包装成工具来实现。

我自己在实际项目里体会最深的一点是:声明式 Agent 的上限取决于工具描述的质量,而不是框架本身。同样的框架,工具描述写得好的团队,Agent 可靠性可能是写得差的团队的几倍。所以与其纠结框架选型,不如把精力花在打磨工具描述和返回值上,这个投入产出比高得多。

相关新闻

细粒度情感分析实战:SemEval-2014 Task 4数据集解析与BERT预处理

细粒度情感分析实战:SemEval-2014 Task 4数据集解析与BERT预处理

简介&#xff1a;这是一份用于细粒度情感分析与方面级情感分析的标准评测数据集&#xff0c;面向NLP研究者、算法工程师及高校相关专业学生&#xff0c;可支撑情感词典构建、情感目标抽取、情感极性判断以及深度学习模型的训练与评估。压缩包共11个文件&#xff0c;以10个XML标…

2026/10/20 7:06:52 阅读更多 →
AI辅助专著撰写全流程:从大纲设计到排版输出的实战工具链

AI辅助专著撰写全流程:从大纲设计到排版输出的实战工具链

写一本专著&#xff0c;工程量到底有多大&#xff1f;过来人都知道&#xff0c;它跟写十篇论文完全是两码事&#xff1a;章节之间的逻辑闭环、术语前后一致、参考文献成体系、图表编号不重不漏、单章两三万字起步……光靠人肉硬扛&#xff0c;写到后半程很容易“前面写什么忘了…

2026/10/20 7:06:52 阅读更多 →
分号不是标点,是代码执行的节拍器与团队契约

分号不是标点,是代码执行的节拍器与团队契约

1. 项目概述&#xff1a;分号不是语法装饰&#xff0c;而是程序员的呼吸节奏“实用经验 40 标示语句结束的分号的思考”——这个标题乍看像一句被遗忘在旧教材角落的语法提醒&#xff0c;甚至有点过时。但如果你最近调试过一段看似逻辑完美、却在某行末尾莫名报错的 JavaScript…

2026/10/20 7:05:52 阅读更多 →

最新新闻

土木人如何用AI技能撬动内推机会,简历秒变稀缺

土木人如何用AI技能撬动内推机会,简历秒变稀缺

想聊清楚这件事&#xff0c;得先承认一个现状&#xff1a;土木行业的内推&#xff0c;从来不是简单的“递简历”&#xff0c;而是一场围绕“信任”和“确定性”的博弈。推荐人把名字压上去&#xff0c;赌的是你入职后能扛住事、不出大纰漏。但大多数土木人的简历&#xff0c;写…

2026/10/20 8:00:21 阅读更多 →
从文件拼接原理到图片木马:二进制安全与检测实战解析

从文件拼接原理到图片木马:二进制安全与检测实战解析

1. 这个标题背后到底在聊什么点进来的朋友&#xff0c;估计大多数是想搞清楚一件事&#xff1a;一张看起来很正常的图片&#xff0c;里面怎么就能“藏”一个程序&#xff1f;这个问题的答案&#xff0c;并不像短视频里演得那么玄乎&#xff0c;本质上就是二进制文件结构的拼接与…

2026/10/20 8:00:21 阅读更多 →
ML307R OpenCPU SDK开发实战:从环境搭建到避坑指南

ML307R OpenCPU SDK开发实战:从环境搭建到避坑指南

简介&#xff1a;ML307R-OpenCpu-sdk包是面向物联网嵌入式开发者的二次开发套件&#xff0c;针对ML307R模组OpenCPU方案&#xff0c;帮助开发者跳过底层驱动搭建&#xff0c;直接在模组上运行应用程序&#xff0c;适用于智能表计、定位终端、工业网关等场景&#xff0c;需要具备…

2026/10/20 8:00:21 阅读更多 →
四个编码器读写软件:参数配置与诊断实战指南

四个编码器读写软件:参数配置与诊断实战指南

简介&#xff1a;这份资源面向工业自动化工程师、设备调试人员及SICK编码器使用者&#xff0c;汇集了4套官方编码器读写与配置软件&#xff0c;重点解决基于HIPERFACE协议设备的通信、参数设置与故障诊断问题。压缩包共57个文件&#xff0c;约28.61MB&#xff0c;以dll动态库、…

2026/10/20 8:00:21 阅读更多 →
训练 AI 的数据去哪里找?开源数据集与商用数据渠道汇总

训练 AI 的数据去哪里找?开源数据集与商用数据渠道汇总

在人工智能技术飞速发展的今天&#xff0c;训练 AI 的数据去哪里找已成为企业与研发团队面临的核心命题。虽然开源数据集为早期研究提供了便利&#xff0c;但在面对垂直领域深耕、模型商业化落地及版权合规要求时&#xff0c;其局限性日益凸显。构建高质量、合规的AI模型&#…

2026/10/20 8:00:21 阅读更多 →
CAT12 VBM全流程实战:从安装配置到多模态形态学建模

CAT12 VBM全流程实战:从安装配置到多模态形态学建模

简介&#xff1a;本资源是面向神经影像学研究者与计算神经科学初学者的CAT12工具箱完整安装与分析套件&#xff0c;专为基于体素的形态计量学&#xff08;VBM&#xff09;研究设计&#xff0c;解决sMRI数据预处理、组织分割、统计建模与结果可视化等核心问题。包内共518个文件&…

2026/10/20 7:59:20 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →