Agent architectures
许多LLM(大型语言模型)应用程序在LLM调用之前和/或之后执行特定的步骤控制流。例如,RAG(检索增强生成)会检索与问题相关的文档,并将这些文档传递给LLM,以便为模型的回答提供依据。
许多LLM(大型语言模型)应用程序在LLM调用之前和/或之后执行特定的步骤控制流。例如,RAG(检索增强生成)会检索与问题相关的文档,并将这些文档传递给LLM,以便为模型的回答提供依据。
https://github.com/asukaminato0721/magic-in-ten-mins-ts/blob/master/doc/ADT.md
https://github.com/asukaminato0721/magic-in-ten-mins-ts/blob/master/doc/GADT.md
https://github.com/asukaminato0721/magic-in-ten-mins-ts/blob/master/doc/CoData.md
原文:Modern Node.js Patterns for 2025,翻译来自DeepSeek-R1
Node.js 自诞生以来经历了显著的变革。如果您使用 Node.js 已有数年,您很可能亲身见证了它的演进——从重度依赖回调、CommonJS 主导的环境,发展到如今基于标准的、简洁的开发体验。这些变化不仅仅是表面的;它们代表了我们在服务器端 JavaScript 开发方法上的根本性转变。现代 Node.js 拥抱 Web 标准,减少外部依赖,并提供更直观的开发体验。让我们探索这些转变,并理解它们为何对您 2025 年的应用程序至关重要。
前面学了如何构建提示词、加载文档/网页内容、检索、嵌入等等,实现简单的RAG,下面再来继续学一下函数调用,让ai调用代码能力实现对ai能力的增强
上一篇讲了基本的调用LLM,还有简单或者灵活的去使用模板生成提示词,以及使用提示词+解析器实现结构化的输出,这一节介绍一下Loader、Embedding和搭建向量数据库,最后实现一个简单的RAG
发布在掘金上的版本
LangChain.js官方文档:https://js.langchain.com/
最近在学RAG,学到Embedding部分,想搭建一个向量库去存数据,看了一圈网上以及问大佬,基本都是推荐milvus这个数据库,所以就开始想搭建一下玩玩。
看官方的部署文档有三种部署方式,轻量、单机、集群,其中轻量的限制linux和mac,我电脑是windows,只有虚拟机linux,而且集群的话又挺麻烦的,就选了单机版。单机版又分了单镜像和compose,单镜像还得用一个脚本来启动,我个人不怎么喜欢脚本(可能是因为不会吧),就选了compose了。
1 | # Download the configuration file |
非常熟悉的docker compose,不过我启动完成后在docker desktop一看竟然没发现刚刚启动的容器,想了一下估计是因为我用sudo启动的,而docker desktop是非root启动,然后他们就被隔离开了导致看不到。
那之后删掉之前的容器,重新在当前用户启动
1 | sudo docker compose down -v |
然后发现怎么还得重新下镜像,原来不同用户连镜像都隔离开了,那好吧,我知道docker有个导出镜像的功能,就将那这几个镜像导出然后再在非root用户下导入(不知道还有没有更方便的办法,有懂的docker的大佬可以评论区告诉我拜托了)。
前面说到我是为了学ai才选了这个数据库,那么部署完成之后我就开始使用,我跑的是Langchain.js的一个demo代码:
1 | import { Milvus } from "@langchain/community/vectorstores/milvus"; |
简单说一下这段代码,就是通过Embedding模型将文本转成向量存到milvus数据库,
但是还没跑完发现代码报错了:Error: 14 UNAVAILABLE: No connection established. Last error: Failed to connect (2025-04-07T02:15:51.637Z)
怎么突然就连不上了,然后去docker desktop看了一下发现容器停掉了,点进去一看全是go的panic报错,看了一下panic前面的日志,提到:Resource requested is unreadable, please reduce your request rate
想了一会之后我记得milvus使用minio作为存储,然后去看了一下minio的日志,发现了确实是minio报错了:
1 | 2025-04-03 15:39:46 API: SYSTEM() |
然后去minio的github搜了一遍也没找到什么解决方法。我突然想到milvus的教程里面是用root用户启动的,我就想了一下用root试了一下。最后发现root启动是没有问题,上面代码也能跑通。那应该可能是权限之类的问题了。
说起来我的docker使用经验也不多,我就去问了一些大佬,大佬看了一下docker-compose.yml之后给了两个方法,一个是特权模式,另一个是将volume的绑定目录改成命名卷。特权模式我试了一下没用,改成命名卷的方式通过问ai,完成修改:
1 | version: '3.5' |
然后测了一下上面的代码,也能正常跑通了,minio和milvus也没有报错了。
这个问题根据github的issue所提,应该是只有在linux6.0之后的版本才会出现。
前文:手把手教你在浏览器和RUST中处理流式传输 提到如何简单的处理流式输出,但是后来发现这个写法有bug,下面讲解一下更好的写法
顺便补充一下,上一篇文章提到的IterableReadableStream
来自@langchain/core
,你可以这样导入使用:
1 | import { IterableReadableStream } from '@langchain/core/utils/stream' |
除了上一章的ndjson以外,最常用就是Event Stream了,包括OpenAi等一众ai服务提供商都会提供sse接口,并且以Event Stream的格式进行输出,先来看看ai是怎么理解Event Stream和SSE的:
Server-Sent Events (SSE) ,一种基于 HTTP 的轻量协议,允许服务器向客户端推送实时数据流。
数据通过 HTTP 流式传输,内容类型为 text/event-stream
。
每条事件由字段组成,用换行符分隔。字段包括:
data
: 事件的具体内容(必填)。event
: 自定义事件类型(可选)。id
: 事件唯一标识符(可选)。retry
: 重连时间(毫秒,可选)。示例:
1 | event: status_update |
那再来看看ai输出的结果:
很标准的text/event-stream
格式
你以为我要像上一篇一样开始手搓处理代码了吗,no no no,我们还是使用langchainjs进行处理,原因后面会提到。
这里推荐一个fetch封装工具:ofetch
,一个类似axios
的库,作用大家应该都懂了吧,这里我拿火山的接口来演示:
1 | // vite.config.js |
如果是webpack的话,一定要关闭devServer
的compress
,不然会导致整个请求结束才返回,这样就不是流式输出了。
1 | // request.js |
1 | import { fetchRequest } from "./request"; |
返回正常,不过要注意,结尾有个[DONE]
,所以不能无脑反序列化,
1 | for await (const chunk of stream) { |
这样就拿到每个chunk了,当然你可以将test方法改成生成器,然后for
里面yield JSON.parse(chunk)
既然大家都知道流式输出是一个一个chunk的方式返回,那么是不是有可能一行的文本,拆分成两个chunk(在js看来是ArrayBuffer)?而一个utf8字符是定长的,可能是1-3字节,那是不是有可能在某个字符的时候,其中一部分字节拆分到一个chunk,然后剩下部分字节拆分到下一个chunk?
这样就会导致你在decode的时候发生报错,无法正常decode成文字,所以langchainjs的方法考虑到这个情况:
上面的webpack配置已经讲解了一下devServer应该怎么配置才能流式输出。还有就是使用nginx代理的时候也需要修改一下配置:
1 | server { |
其实就是关闭一些代理缓冲,以及设置一下缓冲区,为什么要这样设置,这里有请懂nginx配置的大佬细说一下😜
原文链接:https://rust-dd.com/post/our-first-production-ready-rag-dev-journey-in-pure-rust
我们涉足AI开发已有一段时日——不仅仅是那种“调用现有API”的高层次开发,而是那种必须构建无法依赖OpenAI或类似在线系统的解决方案。最近,我们面临了一项挑战:在Rust中创建我们首个生产级RAG(检索增强生成)解决方案。在这篇文章中,我将详细阐述我们所采取的步骤、遇到的困难,以及Rust社区在这一领域的现状。
一切始于我们在rust-dd的探索,当时我们想要构建一个抽象的RAG解决方案。这个想法很简单:我们将向量嵌入存储在某个向量数据库中,并使用标准的向量相似性搜索来为我们的本地LLM检索相关上下文。
大约一个月的时间里,这个项目完全以开源方式运行。我们构建了功能性RAG系统所需的一切:
在这个阶段,我们通过官方的Qdrant fastembed
Rust库使用纯文本来生成嵌入。初步测试看起来非常有希望,然后……真正的挑战开始了。
我们收到了一个工业解决方案的试点请求,期限为1.5个月。这个RAG必须处理未知的数据源,因此我们不能依赖ChatGPT或其他大型成熟模型。我们的资源有限,这意味着我们实际上只能使用14–32B参数的模型——大多数是4位或8位量化形式。
我们无法分享所有细节,但可以说,我们必须回答来自用多种语言编写的PDF文件的查询,包括一种特别奇特的语言。任何尝试处理过PDF的人都知道,这是一个由来已久的难题:
我们测试了几种策略,最终选择了:
由于文档是专业化的,较小的LLM没有足够的内置领域知识。在许多方面,将数据上传到Qdrant比从LLM中获得正确答案更具挑战性。许多博客文章警告说,在RAG解决方案中使用PDF文件有点像是噩梦——我们完全同意!如果你对自己的数据很了解,并且不需要通用解决方案,你可以通过蛮力找到一种不错的方法。但要小心:
突然之间,我们面临着一个关于分块大小、嵌入限制以及寻找完美平衡点的无尽难题。
由于我们需要处理多种语言和专业化文本,我们最终使用了不同的模型,所有这些模型都来自Hugging Face。我们还引入了一个基于BERT的模型,甚至是一个生成稀疏向量的模型。结果是,我们构建了一个比最初预期更复杂的嵌入管道,但这是捕捉这些专业化文本细微差别的必要之举。
随着截止日期的临近,更多问题出现了:
我们不是CUDA开发专家,也没有时间自己修复这些问题,但非常感谢Mistral.rs的维护者Eric Blueher,他已经解决了这些问题。不幸的是,由于时间紧迫,我们转向了Ollama-rs以获取更稳定的框架,而Ollama运行顺利,没有进一步的GPU问题。我们仍然打算最终回归Mistral.rs,因为我们希望完全控制每个组件,而Mistral.rs提供了许多低层配置的可能性。
我们还不得不在Rust中使用Rust-Bert处理基于BERT的模型,但它底层使用了LibTorch。这带来了一些依赖关系上的摩擦。这是我们第一次感觉到,无论好坏,我们现在根本无法避免使用Python。
因此,我们采取了务实的方法:
这为我们提供了所需的嵌入,并且我们可以继续冲刺到终点线。
我们只剩下不到一个月的时间。最大的挑战是弄清楚如何将最佳的向量数据库结果返回给LLM。如果你有巨大的文档和小的分块大小,你可以保持文档的连贯性,但可能会错过大局上下文。如果你有巨大的分块大小,你可能会失去细节或使搜索变得不那么相关。我们尝试了许多“常识性”的解决方案,但结果并不如我们希望的那么好。
最终,我们不得不退一步,真正思考底层数学如何塑造我们的数据。通常,你不仅仅需要遵循最佳实践;你需要深入了解模型的结构以及向量相似性是如何计算的。但时间紧迫,所以我们决定为演示选择我们拥有的最佳解决方案。
终于到了展示我们解决方案的日子。当一切顺利运行时,我们感到无比轻松——甚至有点惊讶:
系统在85-90%的时间内给出了正确的英文答案,几乎没有出现幻觉。
由于整个管道是英文的,其他语言的准确性稍差,但也不落后太多。
总的来说,我们自信地认为,我们已经在紧迫的期限内实现了交付基于Rust的生产级RAG的目标。
我们已经期待提高解决方案的准确性并扩展到更多语言。那些许多个月——以及熬夜的日子——绝对值得。我们的计划是:
这就是我们在Rust中成功构建RAG的故事——我们希望这只是众多成功故事中的第一个!
在Rust中为RAG解决方案工作非常有成就感,但也充满了陷阱。无论是嵌入管道的复杂性、GPU的问题,还是不得不集成Python,构建一个稳健的RAG都是一个多层次的难题。但如果你对Rust和AI充满热情,毫无疑问,社区正在突破界限。我们很高兴能成为这段旅程的一部分——并迫不及待地想看看它接下来会走向何方。
如果你有任何问题或见解(特别是如果你正在处理Rust、GPU推理和复杂嵌入),请随时联系或留言。让我们继续用Rust构建AI的未来!