如何用AI 来做一个RAG 应用 (持续更新)

19 阅读7分钟

首先,明确一下需求 全栈,项目, 对话,基于RAG 来实现

在目前和ai的结对编程中, 虽然ai 逐步强大,但是作为开发者,始终要作为一个更上层的,调度和指挥,不能全部都相信ai ,要有自己的独立思考能力

明确需求

当时,公司内部,上网采用虚拟,机, 很多时候不是特别方便,内部之前开发的智能文档,更多的是基于模型本身的能力,而不是基于每个部门,公司内部的各种具体的代码规范 每次生成的业务组件,或者小模块的,案例,都是外部代码规范,我们公司内很多组件都是自研平台, 生成的代码无法,即可使用,效率低

目标,希望能给文档,或者要求,能结合公司内部的规范,直接生成可用 规范代码 不做模块,项目级的生成.

技术选项

因为目前是希望,能快速落地一个,以下都会配合ai来进行处理

我初始本身不是全栈项目,

这里借助了Tera 来进行构建 初始的 Next.js 运用

prompt

首先我们来借助 ai生成一个专业的资深前端 promot

# Role: Senior Frontend Engineer & Tech Lead

## Profile
你是一位拥有 10+ 年经验的资深前端工程师(Tech Lead 级别)。你不仅精通代码实现,更深谙软件架构、性能优化、用户体验(UX/UI)以及工程化最佳实践。你对代码质量有洁癖,拒绝“能跑就行”的低质量代码。

## Core Competencies
- **Frameworks**: Deep mastery of React (Hooks, Context, Patterns), Vue 3, or Angular (adapt to user request).
- **Language**: TypeScript Wizard (Strict Mode, Advanced Types, Generics).
- **Styling**: Tailwind CSS, CSS Modules, Styled Components, Sass.
- **Performance**: Core Web Vitals, Code Splitting, Lazy Loading, Memoization strategies.
- **Architecture**: Atomic Design, Clean Architecture, State Management (Zustand/Redux/Recoil).
- **Quality**: Jest, Cypress, Playwright, TDD, CI/CD pipelines.

## Coding Philosophy (The "Gold Standard")
在生成代码时,你必须严格遵守以下原则:
1.  **Type Safety**: 永远优先使用 TypeScript。拒绝 `any`,为 PropsStateAPI 响应定义清晰的接口。
2.  **Clean Code**:
    -   变量命名语义化(Self-documenting code)。
    -   遵循 DRY (Don't Repeat Yourself) 和 SOLID 原则。
    -   函数保持短小精悍,单一职责。
3.  **Performance First**: 警惕不必要的重渲染。默认考虑性能边界情况(如大数据量列表)。
4.  **Robustness**: 总是处理 Loading 状态、Error 状态和空数据状态(Empty States)。永远不要让 UI 崩溃。
5.  **Accessibility (a11y)**: 代码默认符合 WCAG 标准(语义化 HTML, ARIA 属性, 键盘导航)。

## Interaction Guidelines
1.  **Think Before You Code**: 在写代码前,先简要分析问题,提出技术方案或组件结构。
2.  **Explain "Why"**: 不要只给代码,要解释为什么这么写(例如:“这里使用 `useMemo` 是为了避免... ”)。
3.  **Challenge Bad Ideas**: 如果用户的需求会导致性能问题或反模式,礼貌地指出并提供更好的替代方案。
4.  **Incremental approach**: 对于复杂任务,分步骤实现,而不是一次性输出一坨巨大的代码块。

## Output Format
- **File Structure**: 明确指出建议的文件路径(例如:`src/components/Button/Button.tsx`)。
- **Code Blocks**: 使用标准 Markdown 代码块,带语言标签。
- **Comments**: 关键逻辑必须包含简短、有意义的注释。

---
**Instruction**: 现在,请等待我的具体需求。一旦我提出需求,请以资深专家的标准进行分析和编码。

技术设计,解耦

**Instruction**: 现在,请等待我的具体需求。一旦我提出需求,请以资深专家的标准进行分析和编码。

你先结合我的 目标 希望能给文档,或者要求,能结合公司内部的规范,直接生成可用 规范代码

不做模块,项目级的生成. 生成一个RAG 前端项目,

目前按照阶段一开发,目标,先为我划分好项目模块,先不添加任何 模版代码

要求,ai ,服务,业务逻辑,ui视图层解耦

image.png

模块分层/阶段的目标

你是一位资深前端架构师,请协助我设计一个 AI 对话式 Web 应用的前端架构。项目目标是:快速构建一个稳定、可交互、可演示的 MVP 前端界面,暂不依赖复杂后端逻辑,重点验证核心交互流程与扩展性设计。 #### 🎯 核心目标(MVP) - 用户能在浏览器中与 AI 进行多轮对话 - 界面需包含完整对话历史、消息流、输入控制 - 所有状态由前端管理(可 mock API),确保离线可演示 - 代码结构清晰,便于后续接入真实 AI 服务、多模型、多模态等能力 #### 🧩 模块划分要求(请按以下模块进行职责定义) 1. 对话列表侧边栏(Conversation List) - 展示用户已创建的对话会话(可本地存储) - 支持新建、切换、删除会话 - 初始可 mock 静态数据 2. 主对话窗口(Chat Panel) - 按时间顺序展示消息流 - 区分“用户消息”与“AI 消息”样式 - AI 消息需支持: - 加载状态(streaming 模拟) - 错误状态(如网络失败) - 重试按钮(点击后重新发送上一条用户消息) 3. 消息输入区(Input Area) - 支持文本输入(textarea) - 发送按钮 + 快捷键(Enter 发送) - 发送过程中: - 显示“暂停/取消”按钮(模拟中断 streaming) - 禁用输入框,防止重复提交 - 设计需考虑未来扩展: - 多模态输入(如上传图片、文件) - 多模型选择(下拉切换 GPT-4 / Claude / 本地模型等) #### ⚙️ 技术约束与原则 - 使用 React + TypeScript + Vite(默认技术栈) - 状态管理:优先使用 useReducer + useContextZustand(避免 Redux 重型方案) - 所有 AI 调用通过 mock service 实现(返回延迟 + 模拟流式响应) - 组件需 类型安全可测试无副作用耦合 - 目录结构需体现关注点分离(如 features/chat, entities/message, shared/ui) #### 📈 演进性要求 - 架构需支持未来轻松替换为真实 AI API(OpenAI / Ollama / 自研) - 输入区设计需预留 插槽(slots) 用于扩展多模态控件 - 消息渲染需支持 自定义渲染器(未来可渲染代码块、图片、表格等) 请基于以上要求: 1. 给出清晰的 模块职责划分图(文字描述) 2. 推荐 状态结构设计(如 message 对象字段) 3. 提供 关键组件接口定义(TypeScript interface) 4. 建议 目录结构 5. 指出 MVP 可省略但需预留扩展点的功能

能先有一个小的闭环场景

image.png

那现在我对于RAG 不是特别熟悉,那我就先引导 ai 来一步步拆解这个工程需求

那么技术选项 先用 常见的lanchin.js 来做项目

那我想先本地通过大模型,模拟对文本进行向量化,然后进行存储,存储完毕,后,我要进行本地验证,当前的逻辑是否可行

image.png

image.png

虽然我初始不理解整体的RAG 逻辑,但是借助ai ,提升,编程,已经做到了本地去验证

完善RAG 对话

思路

  1. 初始化 LLM
  2. 配合 Lanchin 链式模式,将用户输入的内容,和rag 文本库内容,进行统一调度
export const getLLm = async (query: string, onChunk: (chunk: string) => void) => {
  const llm = init();

  // Create the retrieval chain
  const chain = RunnableSequence.from([
    {
      context: async (input: string) => {
        const retrievedDocs = await search(input);
        const context = retrievedDocs.map(doc => doc.pageContent).join("\n\n");
        return context;
      },
      question: (input: string) => input,
    },
    ChatPromptTemplate.fromTemplate(`Answer the question based only on the following context:
{context}

Question: {question}`),
    llm,
    new StringOutputParser(),
  ]);

  const stream = await chain.stream(query);

  for await (const chunk of stream) {
    onChunk(chunk);
  }
};

如果加入历史记忆,可以去利用其存储,并解决

  const chatHistory = history.map(msg =>
    msg.role === 'user' ? new HumanMessage(msg.content) : new AIMessage(msg.content)
  );
 const chain = RunnableSequence.from([
    {
      context: async (input: string) => {
        let searchQuery = input;
        // Only rephrase if we have history
        if (chatHistory.length > 0) {
          try {
            console.log("[RAG] Rephrasing question with history...");
            // We need to pass the input manually to the rephrase chain
            searchQuery = await rephraseChain.invoke({
              chat_history: chatHistory,
              question: input
            });
            console.log(`[RAG] Rephrased Query: "${searchQuery}"`);
          } catch (e) {
            console.error("[RAG] Failed to rephrase question, using original:", e);
          }
        }

        const retrievedDocs = await search(searchQuery);
        const context = retrievedDocs.map(doc => doc.pageContent).join("\n\n");
        return context;
      },
      question: (input: string) => input,
      chat_history: () => chatHistory,
    },
    ChatPromptTemplate.fromMessages([
      ["system", `Answer the question based only on the following context:
{context}`],
      new MessagesPlaceholder("chat_history"),
      ["human", "{question}"],
    ]),
    llm,
    new StringOutputParser(),
  ]);

工程规范

后期规划