一文了解RAG(检索增强型生成)

在这篇文章中,萌叔将介绍RAG技术。 将它与传统的搜索引擎进行对比,并介绍一个完整的RAG实现–lightRAG,详述其技术细节。 1. 传统搜索 传统搜索通常基于倒排索引来进行搜索 1.1 倒排索引 倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案, 是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。 倒排索引是Term到DocID的映射。 1.2 搜索语句示例 Lucene针对某个字段进行搜索样例 title:hello world 就意味着,搜索title为hello,或者包含title关键字的文档 伪代码形如 doc.title contains "hello" OR doc.title contains "world" 假定 “hello”-> [1, 5, 8] “world”-> [5, 8, 10, 12] 然后搜索引擎对2个doc ID集合的求并集,并对所有文档进行打分,选出分数最高的前N个文档。 2. RAG RAG (检索增强生成) 是一种人工智能技术,它结合了信息检索和生成式模型的功能, 以提高生成文本的准确性和相关性。 RAG 先从外部知识库中检索相关信息,然后结合这些信息,使用生成式模型, 生成更准确、更有上下文相关的文本。 提到RAG,需要先聊一聊Embedding models 2.1 Embedding models 2.1.1 高维向量表征文本 嵌入式模型,可以把一段文本转换为纯数值的高维向量 通过嵌入模型(如bge-m3)将文本转换为1024维的数值向量,这个向量包含了文本的语义特征 curl --location 'http://192.168.100.2:21434/api/embed' \ --header 'Content-Type: application/json' \ --data '{ "model": "bge-m3:latest", "input": "这个故事的主题是什么?" }' 2.1.2 向量距离反映语义相似度 当两个向量在向量空间中的距离越近(通常用余弦相似度或欧氏距离衡量) 说明它们对应的文本在语义上越相似 例如"猫"和"猫咪"的向量会比"猫"和"汽车"的向量更接近 ...

June 27, 2025 · 2 min

gomock简明教程

1.定义 gomock是一个用于Go语言的Mock框架, 它可以帮助开发者在单元测试中模拟(Mock)接口的行为, 从而隔离被测试代码与外部依赖,使测试更加独立、稳定和高效。 项目原地址为 golang/mock 2023年6月之后,代码库已经归档,官方推荐使用go.uber.org/mock 2.安装 go install go.uber.org/mock/mockgen@latest 3.生成Mock代码 有文件itf.go mockgen -source=./itf.go -destination=./mock.go -package=mymock -source:指定包含接口定义的源文件。 -destination:用于写入结果源代码的文件。 -package:指定生成的Mock代码所在的包名。 4.单元测试 预设的请求,可以按照顺序调用或非顺序调用 4.1 非顺序调用 import ( "fmt" "go.uber.org/mock/gomock" "testing" ) func TestFoo(t *testing.T) { // 创建Mock控制器 ctrl := gomock.NewController(t) defer ctrl.Finish() m := NewMockFoo(ctrl) m.EXPECT().Bar(1).Return(1) m.EXPECT().Bar(2).Return(2) fmt.Println("--1--", m.Bar(2)) fmt.Println("--2--", m.Bar(1)) } output: --1-- 2 --2-- 1 可以按照任意顺序调用Bar() 4.2 顺序调用 func TestFoo2(t *testing.T) { // 创建Mock控制器 ctrl := gomock.NewController(t) defer ctrl.Finish() m := NewMockFoo(ctrl) gomock.InOrder( // 必须按照指定顺序调用Bar() m.EXPECT().Bar(1).Return(1), m.EXPECT().Bar(2).Return(2), ) fmt.Println("--1--", m.Bar(2)) fmt.Println("--2--", m.Bar(1)) } 单元测试运行错误 ...

May 29, 2025 · 3 min

推荐一个AI的代理服务(收费)

引言 国内这两年崛起的AI大模型,Kimi和Deepseek,已经足够满足日常的使用需要。 但如果你想体验OpenAI,或者想调用dall-e-3模型画图,或者使用国外的其它音频和视频模型。 你可能需要科学上网,并且可能需要使用信用卡进行注册。其实还是非常不方便。 这里推荐一个AI的反向代理服务: openai-hk 这个服务应该是国人开发的,有QQ客服支持 优势 1.支付方便 使用微信支付,不用包月,按照对接口的实际调用量收费。 从个人使用看,计费即时且精准。 2.不需要科学上网,国内可以随意使用 3. 无限速 因为采用账号池多账号轮询的方案,所以没有API的调用频率限速 4. 支持广泛 以下只列出部分 4.1 文字 gpt-3.5 gpt-4o o1-mini o1-preview gemini claude 4.2 图像 dall-e-3 Midjourney 4.3 音乐 suno udio 4.4 视频 kling 可灵 pika 参考 模型定价列表 5. 文档清晰 openai-hk 文档更新比较快, 新的模型也能很快接入。 另外文档中也介绍了社区一些常用的AI软件,可以作为借鉴和参考。 总结 使用openai-hk 减少了对接的烦恼, 支付更方便,计费合理。适合AI研究人员以及想体验OpenAI等产品的用户。 有兴趣的朋友可以试一下。 作者: vearne 文章标题: 推荐一个AI的代理服务(收费) 发表时间: 2025年2月12日 文章链接: https://vearne.cc/archives/40263 版权说明: CC BY-NC-ND 4.0 DEED

February 12, 2025 · 1 min

介绍OpenAI新发布的Operator

前言 2025年1月23日 OpenAI发布了新的Agent: Operator。 这篇文章将分为2个部分 第1部分,萌叔全文翻译OpenAI的官方介绍资料 第2部分,会基于萌叔自己的理解,介绍一下Operator功能要点和可能的实现思路 废话不多说,进入正题 第1部分 针对新的Agent预览版的研究概述,此Agent可以使用它自己的浏览器来执行用户提出的任务。 今天,我们发布了Operator(在新窗口中打开),一个可以访问网页并为你执行任务的智能代理。 它使用自己的浏览器查看网页,并通过输入、点击和滚动与页面交互。 目前,它是一个研究预览版,这意味着它仍有限制,并将根据用户反馈不断改进。 Operator 是我们推出的首批代理之一,这类 AI 能够自主为你完成任务——你只需指定任务,它就会执行。 Operator 可以处理各种重复性的浏览器任务,例如填写表单、订购杂货,甚至创建梗图。 它能够使用人们日常交互的界面和工具,这不仅拓宽了 AI 的应用范围,还能帮助用户节省日常任务的时间,同时为企业提供新的互动机会。 为了确保安全并逐步推广,我们将从小范围开始。 从今天起,Operator 向美国的 Pro 用户开放, 用户可以在 [operator.chatgpt.com](opens in a new window) 访问。 这个研究预览版让我们能够从用户和更广泛的生态系统中学习,不断优化和改进。 我们计划未来将其扩展到 Plus、Team 和 Enterprise 用户,并将这些能力集成到 ChatGPT 中。 Operator 的工作原理 Operator 由一种名为 Computer-Using Agent (CUA) 的新模型驱动。CUA 结合了 GPT-4o 的视觉能力, 并通过强化学习增强推理能力,使其能够与图形用户界面(GUI)交互——即用户在屏幕上看到的按钮、菜单和文本框。 Operator 可以“看见”(通过截图)并“交互”(使用鼠标和键盘能执行的所有操作)网页,因此无需定制 API 集成,就能在网页上执行任务。 如果遇到挑战或出现错误,Operator 能利用其推理能力进行自我纠正。而当它陷入困境并需要帮助时,会将控制权交还给用户,确保流畅且协作的体验。 尽管 CUA 仍处于早期阶段并存在一些限制,但它在 WebArena 和 WebVoyager 这两个关键的浏览器使用基准测试中创造了新的最先进基准成绩。 你可以在我们的研究博客文章中了解更多关于评估方法及 Operator 背后的研究。 ...

February 6, 2025 · 2 min

开源密码管理小工具 vearne/mypwbox

1. 引子 日常总有很多的密码需要存储,除了使用chrome浏览器的密码管理工具,还有一些其它场景,比如二次密码验证。 2019年萌叔开发了一款纯命令行管理工具 vearne/passwordbox 2024年底,我又在AI的帮助下开发了跨平台的App。vearne/mypwbox 2. 特征和用法 Flutter编写,支持跨平台(需要自己build) 支持国际化,当前支持 中文 或 English 支持使用对象存储(S3协议)进行在线同步&备份(可选,默认为离线) 支持一次性密码 任何支持S3协议的对象存储都可以被使用 AWS S3 Aliyun OSS Tencent COS MinIO 如果密码被用于二次验证且形如 otpauth://totp/ut:vearne?algorithm=SHA1&digits=6&issuer=ut&period=30&secret=XXXXXXXXXXXXXXXXXXXXXXXX 则可以显示对应的基于时间的一次性密码 警告: 关键密码不宜存储于电子文档中,最安全的方式是牢记于心。 作者: vearne 文章标题: 开源密码管理小工具 vearne/mypwbox 发表时间: 2025年01月17日 文章链接: https://vearne.cc/archives/40249 版权说明: CC BY-NC-ND 4.0 DEED

January 17, 2025 · 1 min

给大语言模型插上翅膀

1.引言 有人说,智慧是 知识 + 运用知识的能力。从这个角度来看,大语言模型(如ChatGPT) 在某种程度上可以被认为具有一定形式的智慧。然而现在针对大语言模型的使用有一个问题, 就是模型所具有的知识无法保持实时更新。比如某个API文档发生变化,大语言模型仍然只能基于历史知识进行回答。 有没有办法让大语言模型获取最新的知识,而不至于stale。 显然如果它能够调用搜索引擎,获取最新知识,就可以解决这个问题。 事实上,现在kimi的联网搜索和ChatGPT的搜索网页都是为了解决这个问题。 下面让我们看看如何利用function calling来实现大语言模型从搜索引擎获取数据,继而回答用户的提问。 2. 大语言模型通过搜索引擎获取数据 首先,我们来看一个问题:“今天武汉和上海的气温,哪一个更高?” 对于人类而言,我们要解决这个问题,必须经过以下步骤 1)获取武汉和上海的气温数据 2)比较武汉和上海的最高气温,得出结论 下面萌叔提供一个完整的Python程序,看看如何使用function calling来回答这个提问 import json from email.policy import strict from openai import OpenAI import requests from openai._utils import maybe_transform from openai.types.chat import ChatCompletion base_url = "{此处请自行替换}" api_key = "{此处请自行替换}" google_search_key = "{此处请自行替换}" google_search_engine = "{此处请自行替换}" # 定义 google_search 函数(示例) def google_search(query: str) -> str: url = "https://www.googleapis.com/customsearch/v1" query_params = { "key": google_search_key, "cx": google_search_engine, "q": query, "num": 5, } response = requests.get(url, params=query_params) print(response.url) return response.text # 调用大模型 def send_messages(messages): # 函数信息,用于 Function Calling tools = [ { "type": "function", "function": { "name": "google_search", "description": "通过 Google 搜索获取信息", "parameters": { # 参数类型必须是object "type": "object", "properties": { "query": { "type": "string", "description": "需要搜索的关键词或短语", } }, "required": ["query"], "additionalProperties": False, }, "strict": True } }, ] client = OpenAI(api_key=api_key, base_url=base_url) response = client.chat.completions.create( model="gpt-4o-mini", # 确保使用支持 Function Calling 的模型 messages=messages, tools=tools, max_tokens=4096, parallel_tool_calls=True, ) dd = maybe_transform(response, ChatCompletion) data = json.dumps(dd, ensure_ascii=False) print(data) return response.choices[0].message if __name__ == "__main__": # 示例对话 # input_text = input("请输入您的问题:") messages = [ {"role": "system", "content": "你是一个智能搜索机器人。你根据用户提出的问题,利用google_search函数,获取额外信息,然后回答用户的提问"}, {"role": "user", "content": "今天武汉和上海的气温,哪一个更高?"}, ] # 1. **第1次调用AI接口** message = send_messages(messages) call_cout = len(message.tool_calls) print(f"将会发起{call_cout}次调用...") # 2. **在client端调用google_search()** messages.append(message) for tool in message.tool_calls: if tool.function.name == "google_search": parsed_arguments = json.loads(tool.function.arguments) query = parsed_arguments.get("query", None) # 获取 query 值 print("query:", query) content = google_search(query) messages.append({"role": "tool", "tool_call_id": tool.id, "content": content}) # 3. **第2次调用AI接口** message = send_messages(messages) print(f"Model>\t {message.content}") 0) 函数说明 这里我们给出了函数google_search()说明, 并把它传递给大模型, 告诉它在处理问题的时候,可以考虑使用上我们提供的函数。 ...

January 14, 2025 · 3 min

JetBrains IDE整合DeepSeek API实现辅助编程:使用篇

1.引言 在前一篇文章《JetBrains IDE整合DeepSeek API实现辅助编程》中, 萌叔介绍了在JetBrains IDE中安装CodeGPT插件来,借助AI来辅助编程。 这篇文章,萌叔将介绍CodeGPT的使用技巧,方便新手直接上手。 2.使用技巧 2.1 找Bug&代码优化&解释 CodeGPT可以帮助你优化代码。选中一段代码,右键 -> “CodeGPT” -> “Optimize”,CodeGPT会提供优化建议。 Find Bugs/Optimize/Explain 实际调用后端的API是相同的,只是提示词有所不同 示例 上图中的代码中的ioutil.Discard已经不再推荐使用了,我们可以直接让AI帮我们修改一下 上图中是AI给出的回答中,已经帮我们重构了一下。 代码框右上角的几个图标都非常有用。 Diff Selection 对边优化前和优化后的代码,你可以根据需要Accept改动 其它我不再赘述,按字面意思理解即可 2.2 对话 这里的技巧是可以使用 @ 提交文件或者某个文件夹下的所有文件给AI 注意: 请保护隐私数据,不要把敏感数据提交给AI 提交文件,将会消耗更多的token,也就意味着更多的money 作者: vearne 文章标题: JetBrains IDE整合DeepSeek API实现辅助编程:使用篇 发表时间: 2025年01月10日 文章链接: https://vearne.cc/archives/40240 版权说明: CC BY-NC-ND 4.0 DEED

January 10, 2025 · 1 min

JetBrains IDE整合DeepSeek API实现辅助编程

1.起因: 萌叔工作中经常用到JetBrains的IDE,比如Goland和IDEA,最近也在尝试使用AI辅助编程。 JetBrains自带的AI编程助手价格比较昂贵,使用网页版的Kimi和chatGPT咨询编程问题, 需要反复粘贴代码,也不很方便。正好幻方的DeepSeek-V3发布,从它公布数据来看性能非常强,且价格非常便宜。 因此萌叔试着用JetBrains IDE整合DeepSeek API实现辅助编程 2.安装和配置 注意:DeepSeek的API接口兼容openAI的接口 只需找一个可以配置compatible openAI接口的插件即可,这里使用CodeGPT 2.1 安装插件 2.2 配置插件 2.2.1 对话配置 这一步需要你自己去Deepseek创建API Key 传送门 URL地址为 https://api.deepseek.com/chat/completions 注意: 请求Body体中的模型名称需要修改 2.2.2 代码补全配置 注意:这里的URL地址与 2.2.1 对话配置中的不同。 URL地址为 https://api.deepseek.com/beta/completions 实践下来,感觉代码补全功能比较鸡肋,建议直接关闭。 3. 使用 注意:修改一下实际使用的模型 参考资料 1.DeepSeek-V3 正式发布 2.DeepSeek API 文档 3.DeepSeek代码补全 后记 2025年07月14日,插件的名字已经变更为Proxy AI https://plugins.jetbrains.com/plugin/21056-proxyai 作者: vearne 文章标题: JetBrains IDE整合DeepSeek API实现辅助编程 发表时间: 2024年12月31日 文章链接: https://vearne.cc/archives/40235 版权说明: CC BY-NC-ND 4.0 DEED

December 31, 2024 · 1 min

玩转Arduino(4)-天气预报

硬件 ESP32 开发板 0.96寸OLED 128*64 思路 ESP32 开发板自带WIFI和蓝牙 Step1 使用开发板连接家里的WIFI热点 注意修改代码中的WIFI SSID和password Step2 使用和风天气API,获取天气信息 和风天气的API默认访问https://devapi.qweather.com 需要使用SSL,对单片机开发非常麻烦,我重新封装代理了对应接口,简化了调用过程。 可使用 http://qweather.vearne.com/v7/weather/3d?location=101010100&lang=en&key=xxx 访问和风天气API,注意修改秘钥key 代码库为vearne/simpleQweather Step3 解析Response,将天气信息显示在OLED显示屏幕上 代码 完整代码 weather #include <WiFi.h> #include <HTTPClient.h> #include <Arduino.h> #include <Arduino_JSON.h> #include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include "BitmapData.h" #define LOGO_HEIGHT 16 #define LOGO_WIDTH 16 #define WEATHER_X 50 const char *ssid = "vearne-Guest"; // Change this to your WiFi SSID const char *password = "xxxx"; // Change this to your WiFi password // Change key to your api key // https://dev.qweather.com/docs/configuration/project-and-key/ const char *url = "http://qweather.vearne.com/v7/weather/3d?location=101010100&lang=en&key=xxx"; #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(115200); while (!Serial) { delay(100); } // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); for (;;) ; // Don't proceed, loop forever } // We start by connecting to a WiFi network Serial.println(); Serial.println("******************************************************"); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } void loop() { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; // 开始请求 // http.addHeader(const String &name, const String &value) http.begin(url); // 发送 GET 请求 int httpCode = http.GET(); // 如果请求成功 if (httpCode > 0) { // 读取响应 String payload = http.getString(); Serial.println("响应内容:"); Serial.println(payload); // 结束请求 http.end(); JSONVar weatherData = JSON.parse(payload); if (JSON.typeof(weatherData) == "undefined") { Serial.println("Parsing input failed!"); return; } // 获取第一天的天气信息 JSONVar firstDay = weatherData["daily"][0]; String tempMax = (const char *)firstDay["tempMax"]; String tempMin = (const char *)firstDay["tempMin"]; String windScale = (const char *)firstDay["windScaleDay"]; String fxDate = (const char *)firstDay["fxDate"]; String iconDay = (const char *)firstDay["iconDay"]; switch (iconDay.toInt()) { case 100: drawOLED(sun, fxDate, tempMin, tempMax, windScale); break; case 101: drawOLED(cloudy, fxDate, tempMin, tempMax, windScale); break; case 102: drawOLED(cloud, fxDate, tempMin, tempMax, windScale); default: drawOLED(rain, fxDate, tempMin, tempMax, windScale); break; } } else { Serial.println("WiFi 未连接"); } delay(1000 * 300); // 等待 5min } } void drawOLED(const uint8_t *bitmap, String dateStr, String tempMin, String tempMax, String windScale) { display.clearDisplay(); // weather icon display.drawBitmap( 10, 10, bitmap, LOGO_WIDTH, LOGO_HEIGHT, 1); // temp & wind display.setTextSize(1); // Normal 1:1 pixel scale display.setTextColor(WHITE); // Draw white text display.setCursor(WEATHER_X, 10); display.print("temp: "); String s = ""; s.concat(tempMin); s.concat("~"); s.concat(tempMax); display.println(s); display.setCursor(WEATHER_X, 25); display.print("wind: "); display.println(windScale); // line display.drawLine(0, 45, display.width() - 1, 45, WHITE); // date display.setTextSize(2); // Normal 1:1 pixel scale display.setTextColor(WHITE); // Draw white text display.setCursor(5, 50); // Start at top-left corner display.println(dateStr); display.display(); delay(1000); } 库依赖 1.Adafruit_SSD1306 2.Arduino_JSON 3.ArduinoHttpClient 4.WiFi ...

November 23, 2024 · 3 min

试玩单片机的进展和感想

警告:本文仅用于萌叔自己总结之用,对其它人而言可能毫无营养,没有阅读价值。 1.总结 自2024年10月接触Arduino Uno电子平台以来, 萌叔购买了2块开发板和一系列的配件,尝试使用这些配件,并完成了若干小实验。具体如下 1.玩转Arduino(1)-自制温度湿度计 2.玩转Arduino(2)-按钮控制小灯 3.玩转Arduino(3)-感应式垃圾桶 4.玩转Arduino(4)-天气预报 2. 感想 其实感想才是萌叔想重点说的 2.1 单片机的自动化控制相对软件开发要简单很多 生活中大部分的单片机使用的场景都非常简单,比如停车场的自动升降杆、感应式垃圾桶。对应的代码开发量非常的少。相对而言,现在的软件开发非常的复杂,一个普通工程师需要掌握多种开发语言,好几种框架云原生等等卷的飞起。 2.2 整个单片机开发过程,犹如搭建积木 每种配件厂商几乎都有完善且对应的驱动包,特别典型的是步进电机。每种配件相当于一块积木,普通开发者通常只需要使用配件对应的驱动,就可以构建出非常炫酷的功能。最终达到的效果是1 + 1 > 2 的。 2.3 配件和开发板之间也有标准协议进行通讯 类似 I2C和 SPI。 2.4 配件底层涉及多学科知识 笔者在2.1 提到的单片机的自动化控制相对软件开发简单,指的是不涉及配件底层知识,单纯调用驱动的接口的情况。 一旦涉及底层,情况就复杂的多。 比如 RF433MHZ 涉及电磁波和电谐振 步进电机涉及电和磁力的转换 2.5 单片机的配套硬件和软件情况 单片机的硬件非常的弱,比如Arduino UNO的MCU ATmega328 运行内存只有2 KB。 以至于很多库(类似JSON和HTTP库)必须重新定制 软件方面是发现了FreeRTOS, FreeRTOS是有进程(Task)调度的。 单片机的场景,硬件性能弱,对应的软件也就简单,反而觉得更适合初学者学习了,萌叔推荐本科教育应该从单片机入手。 2.6 行业壁垒相比过去大幅降低了 这次尝试玩硬件,纯粹是一时兴起,然后萌叔发现上手几乎毫无难度。 这当然要感谢十几年的软件开发经历,但更多的还是要感谢时代的进步。 2.6.1 工业克鲁苏 中国的硬件制造商把开发板的价格卷到只要几十块,配件的价格卷到只有几块。 以前配件和开发板相连,往往还需要电烙铁焊接,现在一般都有对应扩展板,直接插端子就行。 2.6.2 Arduino平台 类似Arduino这种开源平台,使得开发板和配件驱动安装变得非常简单,代码开发调试也非常方便 2.6.1 开源精神 互联网(包含github)提供了完整的驱动和教程 2.6.2 AI 编程中遇到的很多问题多亏了chatGPT,不然我的开发速度要慢很多 3.开发板 ESP32 Arduino Uno 4.配件 编号 硬件设备 是否使用 备注 1 湿度温度传感器 是 2 LCD1602 是 3 按钮 是 4 LED小灯 是 5 舵机 是 6 单路马达 是 7 红外线接收器 是 8 超声波传感器 是 9 实时时钟模块 是 10 红外线传感器 是 11 RFID射频感应模块 否 12 步进电机 是 只能朝一个方向转? 13 OLED显示屏 是 14 旋转电位器 是 范围是0 ~ 978 15 继电器 是 收到高电平后,NO与COM形成通路 16 声音传感器 否 17 小喇叭 否 18 RF433MHZ 否 作者: vearne 文章标题: 试玩单片机的进展和感想 发表时间: 2024年11月22日 文章链接: https://vearne.cc/archives/40207 版权说明: CC BY-NC-ND 4.0 DEED ...

November 22, 2024 · 1 min