ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

React+Node.js构建AI智能体:Paperclip开发框架解析

React+Node.js构建AI智能体:Paperclip开发框架解析

1. 项目概述:Paperclip 不是回形针,而是一个被严重误读的 AI 智能体开发框架

“Paperclip”这个词在中文技术社区里,最近正经历一场奇特的语义漂移。它本该是英文中一个再普通不过的办公用品——回形针,但在当前的开发者讨论中,它高频地与OpenClaw、Claude、React、Node.js这些词捆绑出现,甚至出现在“openclaw无法安全验证”“claude code安装失败”“sl2环境请运行wsl --status”这类具体报错场景里。我第一次看到“paperclip”被当作项目名提出来时,也下意识去搜了 npm 上有没有叫 paperclip 的包,结果发现它确实存在,但只是一个轻量级的 React UI 组件库(类似 Ant Design 的极简版),和 AI、智能体、Claude 完全不沾边。这说明什么?说明当前社区里大量人在用“paperclip”作为某个未正式命名、或内部代号、或概念性项目的模糊指代——它不是官方术语,而是一种集体无意识的“代称黑话”。

这个现象背后,其实指向一个非常真实的技术演进趋势:基于 React 模式构建能思考与行动的 AI 智能体。你翻看那些“openclaw ubuntu安装教程”“vscode配置claude code”“claude code调用lmstudio的本地模型”的帖子,核心诉求高度一致:不是单纯调 API,而是要在一个可交互、有状态、能渲染 UI、能触发动作的前端环境中,把大模型的能力“编织”(clip)进真实工作流里。React 的组件化、状态驱动、事件响应机制,天然适配这种“AI 即函数、UI 即接口”的新范式。而 Node.js 则承担了后端胶水层的角色——处理模型路由、本地模型加载(如 LMStudio)、文件系统操作、CLI 工具封装等。所以,“paperclip”在这里,本质上是一个隐喻:它代表一种将大模型能力像回形针一样,牢固、灵活、可拆卸地“夹”在现有开发栈(React + Node.js)上的实践方法论。

它解决的不是某个具体 bug,而是一类结构性问题:当 Claude、Qwen、DeepSeek 等模型能力越来越强,但官方 SDK 和桌面客户端(如 Claude Desktop)又受限于权限、网络、组织策略(比如“your organization has disabled claude subscription access”)时,开发者需要一条绕过限制、完全自主可控的落地路径。这条路的核心,就是用自己熟悉的 React 写界面,用 Node.js 做调度,把模型变成一个可编程的“服务”,而不是一个黑盒 App。适合谁?不是刚学console.log('Hello World')的新手,而是已经能独立搭建 React 应用、理解 Node.js 基础 HTTP 服务、并愿意为 AI 能力“亲手造轮子”的中级以上前端/全栈工程师。你不需要从零发明 AGI,但你需要知道怎么让一个useState的值,最终触发一次对本地 Qwen2.5-3B 模型的推理请求,并把返回的 Markdown 渲染成带折叠代码块的富文本。这才是“paperclip”真正要干的事。

2. 核心设计思路:为什么是 React + Node.js 而不是 Electron 或纯前端?

2.1 拒绝 Electron:轻量化与进程隔离的硬需求

很多初学者一想到“做个带 UI 的 AI 工具”,第一反应就是 Electron。毕竟它成熟、生态好、能打包成桌面应用。但“paperclip”类项目恰恰要避开它,原因很实际:内存开销、更新复杂度、以及最关键的——模型运行环境冲突。Electron 主进程和渲染进程都基于 Chromium,而像 LMStudio 这样的本地模型服务,本身就是一个独立的、基于 Rust 的 HTTP 服务器(默认监听http://localhost:1234/v1/chat/completions)。如果你把 LMStudio 的启动逻辑塞进 Electron 的主进程,就等于让一个浏览器内核去管理另一个重型服务进程,极易出现端口占用、SIGTERM 信号处理失败、Windows 下的wsl --status报错(因为 WSL2 虚拟机平台未启用,而 Electron 的某些沙箱机制会干扰其检测)等问题。我实测过,在 Electron 中用child_process.spawn启动 LMStudio,一旦用户关闭窗口,LMStudio 进程经常残留,下次启动就报EADDRINUSE。而“paperclip”的设计,是让 Node.js 后端作为一个纯粹的、无 GUI 的“调度中心”,它只负责一件事:监听 React 前端发来的/api/chat请求,然后用fetch或axios转发给http://localhost:1234/v1/chat/completions,再把响应原样返回。前后端物理分离,进程互不干扰,调试时npm run dev(前端)和npm run server(后端)可以独立重启,这是稳定性的基石。

2.2 拒绝纯前端:绕过 CORS 与模型调用权限的现实枷锁

另一个常见误区是试图用纯前端(React +useEffect+fetch)直连 Claude 或 OpenAI 的官方 API。这在开发阶段看似简单,但上线即死。原因有二:一是所有主流大模型 API 都严格校验Origin头,浏览器同源策略会让fetch('https://api.anthropic.com/v1/messages')直接被拦截,报CORS error;二是 API Key 绝对不能暴露在前端代码里,否则打包后任何人都能从node_modules或浏览器 DevTools 里抠出来。有人会说“用 Vercel Edge Functions 代理”,但这又引入了第三方依赖和网络延迟。而 Node.js 后端天然规避了这两个问题:服务端发起请求没有 CORS 限制,API Key 可以安全地存在.env文件里,通过process.env.ANTHROPIC_API_KEY读取。更重要的是,它提供了“协议转换”的灵活性。比如,你的前端 React 组件期望接收一个{ status: 'success', data: { content: string } }结构,但 Claude 的官方响应是嵌套多层的message.content[0].text。Node.js 层可以做干净的适配,把脏活累活都消化掉,让前端只关心“我点了发送按钮,然后渲染 content 字符串”,其他一概不管。这种关注点分离,是工程可维护性的分水岭。

2.3 React 的不可替代性:状态即智能体记忆,组件即行为单元

为什么非 React 不可?因为它的state和hooks机制,完美映射了智能体的核心抽象。一个典型的 AI 助手需要记住对话历史(messages: Array<{role: 'user'|'assistant', content: string}>)、当前输入框内容(inputValue: string)、是否正在加载(isLoading: boolean)、错误信息(error: string | null)。这些不是零散变量,而是一个紧密耦合的状态机。React 的useState和useReducer让你用声明式的方式定义这个状态机,useEffect则自动处理副作用(比如inputValue变化时,自动聚焦输入框;messages更新时,自动滚动到底部)。更关键的是useCallback和useMemo,它们让你能安全地将“调用模型”的函数作为 prop 传递给子组件(比如一个SendButton),而不用担心每次渲染都创建新函数导致子组件无谓重绘。这直接对应了“智能体行为”的封装:SendButton不关心模型在哪、怎么调,它只执行onSend(inputValue)。这种解耦,让“paperclip”的扩展变得极其简单——今天用 Claude,明天想换成本地 Qwen2.5-3B,你只需要改 Node.js 后端的转发逻辑,React 前端一行代码都不用动。这就是框架选型背后的深层逻辑:不是为了炫技,而是为了在变化中守住稳定。

3. 核心细节解析:从零搭建一个可运行的 Paperclip 基础框架

3.1 环境准备:绕过 Windows 下最头疼的 WSL2 和虚拟机平台报错

在 Windows 上部署任何依赖本地模型的服务(LMStudio、Ollama),第一步几乎必踩坑:“claude's workspace requires the virtual machine platform on windows. enable”。这不是 Claude 的错,而是因为你没开启 WSL2。网上教程常让你去 BIOS 开 VT-x,但其实 90% 的情况,问题出在 Windows 功能开关上。正确步骤是:以管理员身份打开 PowerShell,逐条执行:

# 启用 Windows 子系统(WSL) dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart # 启用虚拟机平台(这是关键!很多教程漏了这步) dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart # 重启电脑(必须!) shutdown /r /t 0

重启后,再运行wsl --install。如果提示wsl --status显示Default Version: 2且状态为Running,才算成功。这一步卡住,后面所有模型服务都无法启动。我见过太多人在这里浪费一整天,最后发现只是少敲了一行dism命令。另外,Node.js 版本也有讲究。热词里提到error installing 24.21.0: node.js v24.21.0 is not yet released,说明社区在追新版本,但生产环境强烈建议用 LTS(长期支持版)。截至 2024 年中,Node.js 20.x 是最稳的选择。下载地址是官网nodejs.org,别信什么“node.js官网下载openclaw”这种误导性搜索词——OpenClaw 和 Node.js 官网毫无关系,它只是一个 GitHub 项目。

3.2 项目结构:清晰划分前端、后端、模型三界

一个健康的 “paperclip” 项目,目录结构必须泾渭分明,杜绝“前端代码里写require('child_process')”这种反模式。我的标准结构如下:

paperclip/ ├── client/ # React 前端(Vite 创建) │ ├── src/ │ │ ├── components/ │ │ │ ├── ChatWindow.tsx # 主聊天界面 │ │ │ ├── MessageItem.tsx # 单条消息(区分 user/assistant) │ │ │ └── SendButton.tsx # 发送按钮(含 loading 状态) │ │ ├── hooks/ │ │ │ └── useChat.ts # 封装所有 API 调用逻辑 │ │ └── App.tsx │ └── index.html ├── server/ # Node.js 后端(纯 ESM,无 Express) │ ├── src/ │ │ ├── routes/ │ │ │ └── chat.ts # /api/chat 路由,核心转发逻辑 │ │ ├── services/ │ │ │ └── modelClient.ts # 封装对 LMStudio/Claude 的调用 │ │ └── index.ts # HTTP 服务器入口 │ └── package.json ├── .env # 存放 ANTHROPIC_API_KEY, LMSTUDIO_URL 等 └── package.json # 根目录,定义 "dev": "concurrently \"npm run client\" \"npm run server\""

这个结构的关键在于:client 和 server 是两个独立的 npm 项目,各自有package.json和node_modules。这样做的好处是依赖隔离。比如 client 用 React 18,server 用 Node.js 20 的原生fetch,互不影响。concurrently包用来并行启动两个服务,npm run dev一键搞定。很多新手喜欢把后端代码塞进src/server,结果vite build时把fs模块也打包进去,导致浏览器报错。物理分离,是避免这类低级错误的最有效手段。

3.3 前端核心:用 React Hooks 构建可预测的智能体状态流

useChat.ts是前端的灵魂,它必须精确控制状态流转,避免竞态条件(race condition)。比如用户快速连续点击发送按钮,不能让多个请求的响应乱序覆盖。我的实现核心是AbortController和ref:

// client/src/hooks/useChat.ts import { useState, useRef, useCallback } from 'react'; export function useChat() { const [messages, setMessages] = useState<Array<{role: string; content: string}>>([]); const [inputValue, setInputValue] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string | null>(null); // 用 ref 存储当前请求的 AbortController,确保每次新请求都能取消旧的 const abortControllerRef = useRef<AbortController | null>(null); const sendMessage = useCallback(async (content: string) => { if (!content.trim() || isLoading) return; // 取消之前的请求 abortControllerRef.current?.abort(); abortControllerRef.current = new AbortController(); try { setIsLoading(true); setError(null); // 添加用户消息到状态(立即生效,UI 响应快) setMessages(prev => [...prev, { role: 'user', content }]); setInputValue(''); // 调用后端 API const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [...messages, { role: 'user', content }] }), signal: abortControllerRef.current.signal, // 关键:绑定 signal }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); // 添加助手消息(注意:这里 messages 是旧值,所以用函数式更新) setMessages(prev => [...prev, { role: 'assistant', content: data.content }]); } catch (err) { if (err.name === 'AbortError') { console.log('Request cancelled'); } else { setError(err instanceof Error ? err.message : 'Unknown error'); } } finally { setIsLoading(false); } }, [messages, isLoading]); return { messages, inputValue, setInputValue, isLoading, error, sendMessage, }; }

这段代码的价值在于:它把“用户输入 -> 显示用户消息 -> 请求模型 -> 显示助手消息”的整个流程,封装在一个可复用、可测试的 hook 里。setMessages的两次调用(先用户后助手)保证了 UI 的即时反馈,而AbortController则是应对用户“手抖”的终极方案。这比任何第三方状态管理库都更轻量、更可控。

4. 实操过程:从启动 LMStudio 到在 React 中渲染带语法高亮的代码块

4.1 模型服务启动:LMStudio 的静默模式与端口配置

LMStudio 是目前 Windows/macOS 上最友好的本地模型 GUI,但它默认启动的是一个带完整 UI 的桌面程序,这对“paperclip”来说是冗余的。我们需要的是它的 HTTP API 服务。启动方式很简单:下载 LMStudio 后,不要双击图标,而是打开终端,cd 到安装目录,执行:

# macOS/Linux ./LMStudio.app/Contents/MacOS/LMStudio --no-gui --port=1234 --host=127.0.0.1 # Windows (PowerShell) .\LMStudio.exe --no-gui --port=1234 --host=127.0.0.1

--no-gui参数是关键,它让 LMStudio 以纯服务模式运行,不弹窗、不占 Dock/任务栏。--port和--host确保它监听在http://localhost:1234,这是 Node.js 后端默认转发的目标。启动后,访问http://localhost:1234,你应该能看到一个简单的 Swagger 文档页,证明服务已就绪。此时,你可以用curl测试:

curl -X POST "http://localhost:1234/v1/chat/completions" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5-3b", "messages": [{"role": "user", "content": "你好"}] }'

如果返回了 JSON,说明模型加载成功。注意:首次加载模型会很慢(可能 2-3 分钟),因为要从磁盘加载权重到显存。耐心等待,不要反复重启。

4.2 后端转发:Node.js 的 fetch 与流式响应处理

server/src/routes/chat.ts是连接前后端的桥梁。它接收前端 POST 的messages数组,转发给 LMStudio,并把响应透传回去。但有一个重要细节:LMStudio 支持流式响应(streaming),即边生成边返回 token,这对用户体验至关重要(避免白屏等待)。Node.js 的fetch原生支持流,但需要手动处理ReadableStream:

// server/src/routes/chat.ts import { createRequire } from 'module'; const require = createRequire(import.meta.url); const { Readable } = require('stream'); export async function handleChat(req: Request) { if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 }); const { messages } = await req.json(); // 构造 LMStudio 请求体 const lmStudioBody = { model: 'qwen2.5-3b', messages, stream: true, // 关键:开启流式 }; try { const lmStudioRes = await fetch('http://localhost:1234/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(lmStudioBody), }); if (!lmStudioRes.ok) { const errorText = await lmStudioRes.text(); return new Response(errorText, { status: lmStudioRes.status }); } // 将 LMStudio 的流式响应,转换为一个新的流,供前端消费 const reader = lmStudioRes.body.getReader(); const stream = new ReadableStream({ async start(controller) { while (true) { const { done, value } = await reader.read(); if (done) break; controller.enqueue(value); } controller.close(); } }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', } }); } catch (err) { console.error('LMStudio fetch error:', err); return new Response(JSON.stringify({ error: 'Model service unavailable' }), { status: 503, headers: { 'Content-Type': 'application/json' } }); } }

这段代码的精妙之处在于ReadableStream的包装。它让前端可以用EventSource或fetch().body.getReader()来消费流,实现真正的“打字机效果”。没有它,前端只能等到整个响应完成才渲染,体验断层。

4.3 前端渲染:用 remark 和 rehype 解析并高亮模型返回的 Markdown

模型返回的content通常是 Markdown 格式,包含代码块(python ...)。直接dangerouslySetInnerHTML是危险的,且无法高亮。我的方案是:在前端用remark-parse+remark-rehype+rehype-stringify+rehype-highlight这套组合拳,纯客户端解析:

npm install remark-parse remark-rehype rehype-stringify rehype-highlight

然后在MessageItem.tsx中:

import remarkParse from 'remark-parse'; import remarkRehype from 'remark-rehype'; import rehypeStringify from 'rehype-stringify'; import rehypeHighlight from 'rehype-highlight'; import { unified } from 'unified'; interface MessageItemProps { content: string; } export function MessageItem({ content }: MessageItemProps) { // 使用 unified pipeline 解析 markdown const processedHtml = unified() .use(remarkParse) .use(remarkRehype) .use(rehypeHighlight) // 自动高亮代码块 .use(rehypeStringify) .processSync(content) .toString(); return ( <div className="message-content"> <div dangerouslySetInnerHTML={{ __html: processedHtml }} /> </div> ); }

rehype-highlight会自动识别代码块的语言标识(如 ```python),并注入<pre><code class="language-python">,配合highlight.js的 CSS,就能实现专业级语法高亮。这比后端渲染更灵活,因为高亮主题可以随前端主题切换,且无需后端额外依赖。

5. 常见问题与排查技巧实录:那些只有踩过才知道的坑

5.1 “openclaw无法安全验证”与“claude native binary not installed”的本质

这两个错误,表面不同,根源一致:它们都源于对“OpenClaw”这个名词的误解。OpenClaw 并不是一个可下载、可安装的软件包,它是一个开源项目(GitHub 上open-claw/openclaw),其核心是一个用于构建 AI 智能体的 TypeScript 库,类似于 LangChain,但更轻量、更 React 原生。所谓“无法安全验证”,是因为你在 npm install 时,可能误装了一个名字相似的恶意包(npm 生态里常有openclaw、open-claw、openclaw-core等混淆包)。正确的安装命令永远是:

npm install @open-claw/core # 或者,如果你要用它的 CLI 工具 npx @open-claw/cli@latest init my-agent

而error: claude native binary not installed,则是因为你试图全局安装claude-codeCLI,但它的 postinstall 脚本(一个下载预编译二进制的脚本)失败了。根本原因通常是网络问题或权限不足。解决方案不是重试,而是绕过 CLI,直接用 HTTP API。claude-code的桌面版,底层就是个 Electron 封装的fetch调用。你完全可以用自己的 Node.js 后端,构造符合 Anthropic 规范的请求头(x-api-key,anthropic-version),直接调用https://api.anthropic.com/v1/messages。这样既避开了二进制下载,又获得了完全控制权。

5.2 “react native 启动白屏”与“openclaw obsidian”的启示

“react native 启动白屏”这个热词,看似无关,实则揭示了一个关键矛盾:移动端的 React Native 与桌面端的 AI 智能体开发,面临完全不同的约束。RN 白屏,90% 是 Metro 打包器找不到入口文件或资源路径错误;而“openclaw obsidian”则指向另一个方向:Obsidian 是一个基于 Electron 的笔记软件,它允许用插件(Plugin)扩展功能。有人尝试把 OpenClaw 的逻辑做成 Obsidian 插件,让用户在笔记里直接调用 AI。这启发我们:“paperclip”的终极形态,未必是一个独立 App,而可能是一个 VS Code 扩展、一个 Obsidian 插件、甚至一个 Chrome 浏览器插件。它的核心价值在于“能力”,而非“载体”。所以,当你遇到react native白屏时,不要钻牛角尖,想想:这个 AI 能力,是否真的需要在手机上运行?还是说,一个 PWA(渐进式 Web App)+ Service Worker 缓存,就能满足 80% 的场景?把精力放在能力封装上,载体可以随时替换。

5.3 “qwen2.5-3b 关联到openclaw”与模型路由的动态配置

如何让同一个“paperclip”框架,既能调 Claude,又能调 Qwen,还能调 DeepSeek?硬编码 URL 是下策。我的做法是在 Node.js 后端引入一个简单的模型路由表:

// server/src/config/models.ts export const MODEL_ROUTES = { 'claude-3-haiku-20240307': { type: 'anthropic', baseUrl: 'https://api.anthropic.com/v1', apiKey: process.env.ANTHROPIC_API_KEY!, }, 'qwen2.5-3b': { type: 'lmstudio', baseUrl: 'http://localhost:1234/v1', }, 'deepseek-coder-33b-instruct': { type: 'ollama', baseUrl: 'http://localhost:11434/api', } } as const; export type ModelId = keyof typeof MODEL_ROUTES;

然后,前端在发送请求时,带上modelId字段:

{ "messages": [...], "modelId": "qwen2.5-3b" }

后端根据modelId查表,选择对应的baseUrl和认证方式,动态构造请求。这样,增加一个新模型,只需在models.ts里加一行配置,前后端都不用改。这比任何“通用 react 开发标准”都更实用,因为它把变化点(模型)和不变点(通信协议)彻底分离。

提示:在package.json的scripts里,为不同模型准备快捷启动命令,比如"start:qwen": "cd server && npm run dev && cd ../client && npm run dev",能极大提升切换效率。

注意:process.env.ANTHROPIC_API_KEY!后面的!是 TypeScript 的非空断言,表示你确定这个环境变量一定存在。生产环境务必用if (!process.env.ANTHROPIC_API_KEY) throw new Error('Missing ANTHROPIC_API_KEY')做防御性检查,否则服务启动失败时,错误日志会非常难追踪。

6. 进阶扩展:从单模型聊天到多智能体协作工作流

6.1 “workbuddy这种是不是也都参考了openclaw才搞出来的?”——智能体编排的必然性

WorkBuddy、Cursor、GitHub Copilot 这些产品,其底层逻辑确有共通之处:它们都不是在做一个“更好的聊天框”,而是在构建一个“AI 工作流引擎”。一个典型的工作流可能是:用户输入“帮我重构这个函数”,系统先调用一个“代码分析智能体”理解上下文,再调用“重构建议智能体”生成 diff,最后调用“测试验证智能体”运行单元测试。这正是 OpenClaw 库的设计初衷——提供Agent、Tool、Workflow等抽象。在“paperclip”框架里,我们可以轻松集成:

// server/src/agents/refactorAgent.ts import { Agent } from '@open-claw/core'; export const refactorAgent = new Agent({ name: 'refactor-agent', description: 'Analyzes and suggests refactorings for JavaScript/TypeScript code', tools: [ // 定义一个工具:调用 AST 解析服务 { name: 'parseCodeAST', description: 'Parses source code into an abstract syntax tree', parameters: { code: 'string' }, execute: async (params) => { // 调用本地 AST 服务,返回 AST JSON return await fetch('http://localhost:3001/ast', { method: 'POST', body: JSON.stringify({ code: params.code }) }).then(r => r.json()); } } ], // 定义工作流:先 parse,再 suggest workflow: async (input) => { const ast = await this.tools.parseCodeAST({ code: input.code }); // 调用 LLM 生成重构建议... return { suggestions: [...] }; } });

然后,在chat.ts路由里,根据用户意图动态选择 agent:

// 如果用户消息包含 "refactor" 关键词,就用 refactorAgent if (input.messages.at(-1)?.content.toLowerCase().includes('refactor')) { const result = await refactorAgent.workflow(input); return Response.json(result); }

这不再是简单的“问答”,而是“任务执行”。WorkBuddy 的成功,证明了这种模式的巨大潜力。

6.2 “claude刷新物理学世界纪录”带来的启示:领域知识注入

最近 Claude 在物理竞赛题上的突破,核心不是模型更大,而是领域知识的精准注入。一个“paperclip”项目,如果只做通用聊天,很快就会陷入同质化。真正的壁垒,在于垂直领域。比如,为生物信息学研究员打造的版本,可以在useChathook 里,自动将用户提问中的基因名(如TP53)链接到 Ensembl 数据库;为律师打造的版本,可以内置法律条文向量库,让模型回答时自动引用《民法典》第 XXX 条。实现方式很简单:在 Node.js 后端,添加一个knowledgeService.ts,它不调大模型,而是查本地 SQLite 或向量数据库。当用户提问时,先用关键词提取(如正则匹配\b[A-Z]{2,}\d+\b提取基因 ID),再查知识库,把检索到的上下文,作为system prompt的一部分,拼接到messages数组里,再发给模型。这样,模型的回答就天然带有了领域深度。这比任何“react 面经”或“react state与hooks”的技巧都更能体现工程师的价值——你不是在写代码,而是在构建一个领域的认知增强系统。

我在实际使用中发现,最有效的知识注入,往往来自最朴素的规则。比如,一个财务助手,不需要复杂的 RAG(检索增强生成),只要在发送请求前,用正则把用户输入里的“¥1000”替换成“人民币壹仟元整”,再交给模型,生成的报销单就立刻显得专业可信。技术的精妙,有时就藏在这些不起眼的字符串处理里。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进