从“酸辣土豆丝”到“马铃薯做法”:手把手教你用 RAG 实现语义搜索
当用户搜“马铃薯怎么做”时传统搜索还在执着地匹配“马铃薯”三个字而语义搜索已经聪明地把“酸辣土豆丝的做法”推到了第一位。这背后就是 RAG 的魅力。写在前面前几天一个朋友问我“你们的搜索怎么这么笨我搜‘马铃薯怎么做’出来的都是带‘马铃薯’的文章但明明有一篇‘酸辣土豆丝的做法’更符合我的需求啊。”这个问题做搜索的兄弟们应该都遇到过。传统的关键词匹配说白了就是LIKE %土豆%在语义理解面前就像是一个只会死记硬背的学生——你问“马铃薯”他只知道找“马铃薯”完全不知道“马铃薯”和“土豆”是一回事。那怎么让搜索变得更聪明答案就是RAG检索增强生成。今天我就带着大家从零开始用 Node.js 实现一个完整的语义搜索引擎。全文约 6000 字预计阅读时间 12 分钟读完你就能在自己的项目里落地语义搜索。搜索的进化史就是从“匹配字符”到“理解含义”的进化史。一、RAG 是什么能当饭吃吗RAG 全称Retrieval-Augmented Generation即检索增强生成。它由三个核心环节组成Retrieval检索从知识库中找到与问题最相关的内容Augmented增强将检索到的内容作为上下文增强提示词Generation生成大模型基于增强后的提示词生成答案但在实际落地中RAG 的第一步——语义检索——就已经能解决 80% 的搜索问题了。我们今天要做的就是这第一步。本质上语义检索做的事情很简单把所有的文本内容提前“编码”成向量一堆数字用户搜索时把搜索词也“编码”成向量计算向量之间的相似度返回最相似的结果说白了就是把文字变成数字然后在数字空间里找“邻居”。二、技术选型为什么选这些组件选择理由LLM API阿里云 DashScope国内可用embedding 模型text-embedding-v4性价比高Node.js最新 LTS支持原生await代码更简洁向量存储JSON 文件小规模场景足够无需额外部署相似度计算余弦相似度业界标准效果好小规模场景用 JSON 文件存向量完全够用。如果数据量超过 10 万条再考虑 Milvus、Pgvector 等向量数据库。三、实战从零搭建语义搜索引擎3.1 项目结构rag-semantic-search/ ├── data/ │ ├── posts.json # 原始数据 │ └── posts-embedding.json # 向量化后的数据 ├── app.service.mjs # LLM 客户端封装 ├── create-embedding.mjs # 批量向量化脚本 ├── semantic-search.mjs # 语义搜索引擎 └── index.mjs # 测试 embedding 接口3.2 第一步封装 LLM 客户端我们先创建一个通用的服务层模块负责与阿里云 DashScope 的 API 交互。这个模块会被其他脚本复用。// app.service.mjs // 导入 OpenAI 官方 SDK它提供了与 OpenAI 兼容接口的调用能力 import OpenAI from openai; // 导入 dotenv用于加载 .env 文件中的环境变量如 API Key import dotenv from dotenv; // 执行配置加载使 process.env 中注入 .env 的内容 dotenv.config(); // 模块化输出 client方便复用 // 这就是大型项目的服务层风骨 export const client new OpenAI({ // 从环境变量读取 API Key避免硬编码泄露 apiKey: process.env.DASHSCOPE_API_KEY, // 阿里云 DashScope 的兼容模式地址必须带 /compatible-mode/v1 后缀 baseURL: https://dashscope.aliyuncs.com/compatible-mode/v1 });代码详解import OpenAI from openai引入官方 SDK它封装了 HTTP 请求提供embeddings.create、chat.completions等方法。import dotenv from dotenv和dotenv.config()让 Node.js 读取项目根目录的.env文件把键值对挂载到process.env上。这里我们用它来存放DASHSCOPE_API_KEY。export const client导出单例客户端所有模块共用同一个实例避免重复初始化。apiKey和baseURL前者是鉴权凭证后者指向阿里云的兼容网关。注意 baseURL 末尾不要加多余斜杠。踩坑提醒baseURL一定要写对不同厂商的兼容模式地址不同。阿里云 DashScope 的兼容模式地址是/compatible-mode/v1。3.3 第二步数据向量化假设我们的原始数据posts.json长这样存放在data/posts.json[ { title: 酸辣土豆丝的做法, category: 美食 }, { title: Vue 3 响应式原理, category: 前端 }, { title: 马铃薯种植技术, category: 农业 } ]现在我们要把这些数据向量化并存储到新的 JSON 文件中。// create-embedding.mjs // 引入 Node.js 内置的 fs/promises 模块支持 Promise 风格的异步文件操作 import fs from fs/promises; // 引入上面封装好的 client复用 LLM 服务 import { client } from ./app.service.mjs; // 定义输入文件路径原始数据和输出文件路径向量化后的数据 const inputFilePath ./data/posts.json; const outputFilePath ./data/posts-embedding.json; // 使用 fs.readFile 异步读取原始 JSON 文件指定 utf-8 编码得到字符串 const data await fs.readFile(inputFilePath, utf-8); // 将 JSON 字符串解析为 JavaScript 数组对象 const posts JSON.parse(data); // 工具函数返回一个 Promise在指定毫秒后 resolve用于限流 const sleep (ms) new Promise(resolve setTimeout(resolve, ms)); // 准备一个空数组用来存放带 embedding 的新数据 const postsWithEmbedding []; // 遍历 posts 数组中的每一项 for (const { title, category } of posts) { console.log(正在向量化: ${title}); // 调用阿里云 embedding 接口 const response await client.embeddings.create({ // 指定模型名称text-embedding-v4 是阿里云最新版 embedding 模型 model: text-embedding-v4, // 关键把需要检索的字段组合成一段语义完整的文本 // 这样模型在编码时会综合考虑标题和分类的语义 input: 标题: ${title}, 分类: ${category} }); // 从响应中提取 embedding 向量是一个浮点数数组 // response.data 是一个数组因为我们只传了一条文本所以取第 0 项 postsWithEmbedding.push({ title, category, embedding: response.data[0].embedding }); // 限流 200ms避免触发 API 频率限制免费或低并发账号尤其重要 await sleep(200); } // 将带 embedding 的数组写入输出文件第三个参数格式化缩进 2 空格便于阅读 await fs.writeFile( outputFilePath, JSON.stringify(postsWithEmbedding, null, 2) ); console.log(✅ 成功向量化 ${postsWithEmbedding.length} 条数据);代码详解import fs from fs/promisesNode.js 从 v10 开始提供fs/promises它把 fs 方法都 Promise 化了。这里我们用readFile和writeFile不用再util.promisify或回调。const data await fs.readFile(...)顶层await在 ES2022 中可以直接在模块中使用无需包裹async函数非常方便。JSON.parse(data)把文件内容字符串转为真正的对象数组。client.embeddings.create()向/embeddings端点发送 POST 请求参数model和input。返回的response.data[0].embedding就是 1536 维的浮点数向量text-embedding-v4 默认输出 1536 维。sleep(200)用setTimeout实现延时防止短时间内大量请求被 API 限流429 错误。JSON.stringify(postsWithEmbedding, null, 2)第三个参数是缩进空格数让 JSON 文件可读性更好。这里有几个关键点input 的构造方式很重要。标题: ${title}, 分类: ${category}比单纯传title效果更好因为让模型知道了文本的上下文语义。为什么要 sleep(200)大部分 API 都有 QPS 限制不加限流容易 429。为什么要存到 JSON 文件向量化是一次性的成本存下来后续检索直接用不需要重复调用 API。金句向量化的成本是一次性的但收益是永久性的——这可能是你项目里 ROI 最高的操作。3.4 第三步实现语义搜索终于到了核心环节。首先需要一个余弦相似度计算函数// semantic-search.mjs // 余弦相似度计算函数衡量两个向量方向上的相似程度值域 [-1, 1] const cosineSimilarity (v1, v2) { // 1. 计算点积对应位置元素相乘后累加 const dotProduct v1.reduce((acc, curr, i) acc curr * v2[i], 0); // 2. 计算向量 v1 的模长欧几里得长度 const lengthV1 Math.sqrt(v1.reduce((acc, curr) acc curr * curr, 0)); // 3. 计算向量 v2 的模长 const lengthV2 Math.sqrt(v2.reduce((acc, curr) acc curr * curr, 0)); // 4. 余弦相似度 点积 / (模长1 * 模长2) // 若两个向量均为零向量则分母为0但实际 embedding 不会全零可忽略 return dotProduct / (lengthV1 * lengthV2); };余弦相似度的值在[-1, 1]之间值越大表示越相似。在文本 embedding 场景中通常值在[0, 1]之间。然后是完整的搜索逻辑包括命令行交互和向量检索// semantic-search.mjs // 导入文件操作模块 import fs from fs/promises; // 导入封装好的 client import { client } from ./app.service.mjs; // 导入 Node 内置的 readline 模块用于创建命令行交互界面 import readline from readline; // 读取已经向量化的数据文件 const inputFilePath ./data/posts-embedding.json; const data await fs.readFile(inputFilePath, utf-8); const posts JSON.parse(data); // 创建 readline 接口绑定标准输入输出流 const rl readline.createInterface({ input: process.stdin, // 标准输入键盘 output: process.stdout // 标准输出屏幕 }); // 处理用户输入的回调函数async 因为内部需要 await const handleInput async (query) { // 1. 将用户的搜索词向量化 const response await client.embeddings.create({ model: text-embedding-v4, input: query // 直接传入搜索词 }); const { embedding } response.data[0]; // 提取向量 // 2. 计算每条数据与搜索词的相似度并排序取 Top 3 const results posts .map(item ({ ...item, // 展开原对象title, category, embedding similarity: cosineSimilarity(item.embedding, embedding) })) .sort((a, b) b.similarity - a.similarity) // 从高到低排序 .slice(0, 3) // 取前 3 条 .map((item, index) ${index 1}. ${item.title} (相似度: ${item.similarity.toFixed(4)}) ) .join(\n); console.log(\n 搜索结果\n${results}); // 继续等待下一次输入形成交互循环 rl.question(\n请输入你要搜索的内容, handleInput); }; // 启动命令行交互首次提问 rl.question(\n请输入你要搜索的内容, handleInput);代码详解import readline from readlineNode 内置模块提供逐行读取输入流的能力我们用它实现命令行问答交互。rl.createInterface({ input, output })创建接口对象绑定process.stdin用户输入和process.stdout输出显示。const handleInput async (query) {...}当用户输入一行文字并按回车后该函数被调用query是用户输入的内容。client.embeddings.create({ input: query })将搜索词转为向量。posts.map(item ({...item, similarity: ...}))对每个帖子计算与搜索词的余弦相似度并作为一个新属性添加。注意这里使用了箭头函数返回对象()包裹对象字面量避免被解析为代码块后面会专门提到这个坑。.sort((a,b) b.similarity - a.similarity)降序排列相似度高的在前。.slice(0, 3)只取前 3 个结果。.map((item,index) ...).join(\n)格式化输出为带序号和相似度的字符串。最后再次调用rl.question()形成循环让用户可以连续搜索无需重启程序。3.5 踩坑记录箭头函数 对象字面量写这段代码时我踩了一个坑分享给大家// ❌ 错误写法箭头函数直接返回对象时{} 会被解析为代码块 const results posts.map(item { ...item, similarity: cosineSimilarity(item.embedding, embedding) }) // ✅ 正确写法 1加括号明确表示返回对象字面量 const results posts.map(item ({ ...item, similarity: cosineSimilarity(item.embedding, embedding) })) // ✅ 正确写法 2显式 return const results posts.map(item { return { ...item, similarity: cosineSimilarity(item.embedding, embedding) }; })箭头函数有两种语法param expression和param { statements }。当你想直接返回一个对象字面量时记得用()包裹否则{}会被当作函数体。3.6 补充测试 embedding 接口index.mjs为了确保 embedding 接口能正常工作我们可以单独写一个测试脚本// index.mjs // 这个文件仅用于快速测试 embedding 是否通 import OpenAI from openai; import dotenv from dotenv; dotenv.config(); const client new OpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: https://dashscope.aliyuncs.com/compatible-mode/v1 }); // 任意输入一段文本看返回的向量长度和结构 const response await client.embeddings.create({ model: text-embedding-v4, input: hs同学大三哦 }); console.log(response.data[0].embedding); // 输出一个 1536 维的数组这段代码不是主流程的一部分但可以帮助你验证 API Key 和网络是否正常。四、效果演示启动程序node semantic-search.mjs测试 1搜索 马铃薯请输入你要搜索的内容马铃薯 搜索结果 1. 马铃薯种植技术 (相似度: 0.9876) 2. 酸辣土豆丝的做法 (相似度: 0.8234) ← 语义关联 3. 红薯的多种吃法 (相似度: 0.6543)测试 2搜索 怎么炒土豆请输入你要搜索的内容怎么炒土豆 搜索结果 1. 酸辣土豆丝的做法 (相似度: 0.9123) 2. 马铃薯种植技术 (相似度: 0.6543) ← 虽然不直接相关但语义上有土豆关联 3. 红烧肉的做法 (相似度: 0.5123)可以看到即使用户搜的是“马铃薯”系统也能把“酸辣土豆丝”推出来——因为它们在向量空间中是“邻居”。五、进阶让检索结果真正增强生成目前的实现只完成了 RAG 中的Retrieval检索。如果想让搜索结果真正变成生成式回答只需要把检索到的内容塞给大模型// rag-chat.mjs (扩展示例) // 假设我们已经有了 results 数组Top 3 检索结果 const answer await client.chat.completions.create({ model: qwen-plus, // 对话模型 messages: [ { role: system, content: 你是一个知识库助手基于提供的上下文回答用户问题。 }, { role: user, content: 上下文 ${results.map(item - ${item.title}: ${item.content}).join(\n)} 用户问题${query} } ] }); console.log(answer.choices[0].message.content);这时候你就完成了完整的 RAG 链路Retrieval从知识库中检索相关片段Augmented将片段作为上下文增强提示词Generation大模型基于上下文生成精准答案金句RAG 的本质就是给大模型配一个外接大脑让它不再依赖训练数据中的记忆。六、优化方向6.1 当数据量变大时用Milvus、Pgvector或Qdrant替代 JSON 文件存储用HNSW或IVF索引加速相似度计算6.2 当检索质量不够好时调整 embedding 的input构造方式加入更多上下文信息使用HyDE假设性文档嵌入技术先用 LLM 生成一个假设答案再用假设答案去检索6.3 当需要多轮对话时把对话历史也纳入 embedding 的上下文中使用Re-Rank模型对初筛结果进行精排七、与 MCP 协议的关联最后简单聊一下 MCPModel Context Protocol。如果你是做 AI Agent 开发的可能会对这个协议感兴趣。MCP 协议本质上是在解决一个问题如何让 AI 模型安全、标准地访问外部工具和数据。在我们的 RAG 场景中如果把“语义搜索”封装成一个 MCP Tool大模型如 Claude通过 MCP 协议调用我们的搜索工具搜索工具返回检索结果大模型基于结果生成最终回答这样做的好处是搜索能力可以被任何支持 MCP 的 Agent 复用不需要每个 Agent 都重新实现一遍。// 伪代码MCP Tool 定义 { name: semantic_search, description: 从知识库中搜索相关内容, inputSchema: { type: object, properties: { query: { type: string } } } }当然MCP 的完整实现超出了本文的范围但它的底层思维和我们的语义搜索是一脉相承的——让 AI 更聪明地获取信息。写在最后回顾一下我们做了什么✅ 理解了 RAG 的核心流程✅ 封装了 OpenAI 兼容的 LLM 客户端app.service.mjs✅ 批量向量化了原始数据create-embedding.mjs✅ 实现了基于余弦相似度的语义搜索semantic-search.mjs✅ 讨论了优化方向和 MCP 协议的关联核心代码加起来不到 200 行但已经能解决传统搜索最头疼的语义鸿沟问题。搜索的进化本质上是从字面匹配到语义理解的跨越。而 RAG 的出现让这个跨越变得触手可及。希望这篇文章能帮你快速上手 RAG 语义搜索。如果觉得有用欢迎点赞收藏也欢迎在评论区交流你的实践经验。

相关新闻

MediaPipe BlazePose 33关键点实战:Python+OpenCV 实现深蹲计数与角度可视化

MediaPipe BlazePose 33关键点实战:Python+OpenCV 实现深蹲计数与角度可视化

MediaPipe BlazePose 33关键点实战:PythonOpenCV 实现深蹲计数与角度可视化在健身领域,准确的动作计数和姿势分析是提升训练效果的关键。传统方法依赖人工观察或穿戴式设备,而基于计算机视觉的解决方案正逐渐成为更高效的选择。本文将带你从零…

2026/7/6 13:26:26阅读更多 →
第48期 OpenAI的钱都花哪了?Capex支出全景调研

第48期 OpenAI的钱都花哪了?Capex支出全景调研

📢 本文同步发布于「阿水助理小Q」,每日一篇AI基础设施深度分析,欢迎关注。 OpenAI的钱都花哪了?Capex支出全景调研 如果你关注AI行业,一定听过一个数字——OpenAI计划2026年在计算上花500亿美元。这不是大模型训练的成…

2026/7/6 13:26:26阅读更多 →
可视化任务与分析(学习笔记)

可视化任务与分析(学习笔记)

本文根据北京大学公开课《数据可视化》(共63讲)的学习笔记整理而成,课程链接: 【公开课】北京大学:数据可视化 这篇笔记是我在学习过程中,围绕课程中可视化任务分析这一核心模块所做的整理,重点…

2026/7/6 13:21:25阅读更多 →
YOLO11-Pose 全栈拆解|OpenPose自底向上PAF热力图、YOLO端到端OKS损失、多场景姿态落地、完整训练推理工程复现

YOLO11-Pose 全栈拆解|OpenPose自底向上PAF热力图、YOLO端到端OKS损失、多场景姿态落地、完整训练推理工程复现

目录 0. 摘要 1. 姿态估计核心认知与行业技术痛点 1.1 视觉任务层级对比:从看见物体到看懂动作 1.2 姿态估计四大核心技术难点 1.3 COCO数据集标准关键点体系 2. 两大主流姿态估计算法全维度拆解 2.1 自底向上流派:OpenPose核心原理(暴力美学架构) 2.1.1 整体网络架…

2026/7/6 14:36:32阅读更多 →
远程办公团队共用 GPT5.5 接口方案

远程办公团队共用 GPT5.5 接口方案

远程办公团队共用 GPT5.5 接口方案远程团队接入 GPT5.5 接口,最容易出问题的不是代码调用,而是“谁在用、用了多少、出错后怎么查”。如果只是把一个 Key 扔到群里,前几天看着省事,后面基本会遇到额度被打满、成员误用生产 Key、日…

2026/7/6 14:36:32阅读更多 →
IX6024 PCIe2.0 @ACP#24通道工业级交换芯片|老旧工控升级与海量边缘IoT时序数据扩容核心(对标ASM1824)

IX6024 PCIe2.0 @ACP#24通道工业级交换芯片|老旧工控升级与海量边缘IoT时序数据扩容核心(对标ASM1824)

一、行业前言:存量工控国产化替换浪潮来袭,PCIe2.0高可靠扩容成为下沉市场刚需2026年国内物理AI与工业智能化产业形成「高端PCIe4.0、中端PCIe3.0、存量工控PCIe2.0」三级并存的完整生态格局。在高端算力集群、中端工作站完成技术迭代的同时,…

2026/7/6 14:36:32阅读更多 →
GSV9001S@普及型4K视频处理芯片|轻量化物理AI可视化普惠落地核心(普及型工控/教育/边缘终端专用)

GSV9001S@普及型4K视频处理芯片|轻量化物理AI可视化普惠落地核心(普及型工控/教育/边缘终端专用)

一、行业前言:物理AI可视化全面下沉,高性价比视频处理成为普惠设备刚需2026年物理AI产业正式进入全场景可视化落地时代,区别于高端超算集群的极致算力渲染、多屏8K超清输出需求,占据市场80%以上增量的中端普惠设备,核心…

2026/7/6 14:36:32阅读更多 →
纺织行业HMI:化纤纺丝的张力控制与卷绕界面

纺织行业HMI:化纤纺丝的张力控制与卷绕界面

化纤纺丝,是让聚合物熔体或溶液从喷丝板中“吐丝成线”的精密过程。丝条的张力是贯穿始终的生命线:张力不稳,会导致断丝、毛丝、条干不匀,最终影响丝饼的成型质量。这里的HMI,是维持数百个纺丝位 “齐步走” 的 “交响…

2026/7/6 14:36:32阅读更多 →
聚簇索引回表与覆盖索引

聚簇索引回表与覆盖索引

一句话InnoDB 的表本身就是一棵以主键为 key 的 ​B 树(聚簇索引)​。普通索引的叶子节点存的是主键值,查数据时需要​回表​。覆盖索引让查询不回表,是性能优化的核心手段。聚簇索引(Clustered Index) 什么…

2026/7/6 14:31:32阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/6 4:26:20阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/6 2:48:33阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/6 0:10:35阅读更多 →
Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南

Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南

Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine 技术架构先行:官方接口的合规应用 你是否曾在BP阶段手忙脚乱&#x…

2026/7/6 0:03:39阅读更多 →
多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理

多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理

多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理 【免费下载链接】mRemoteNG mRemoteNG is the next generation of mRemote, open source, tabbed, multi-protocol, remote connections manager. 项目地址: https://gitcode.com/gh_m…

2026/7/6 0:03:39阅读更多 →
COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南

COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南

COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南在数据分析和处理领域,去重统计是最基础也是最频繁使用的操作之一。当数据量达到亿级规模时,不同的去重统计方法在性能上可能产生天壤之别。本文将基于 5 亿行数据的实…

2026/7/6 0:03:39阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/6 4:45:01阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/6 4:45:01阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/6 4:45:03阅读更多 →