别再手动做表了!带你实现一个强大的Excel GPT

745 阅读6分钟

打造支持上下文与历史对话的 Excel GPT

在上一篇文章中,我们实现了通过调用 AI 接口生成指定内容的 Excel,具备了根据输入生成预览和下载的功能。

今天我们直接做个大的,创建一个拥有用户交互界面的 Excel GPT,并且实现支持上下文和历史对话记录的强大功能!

一、功能升级

相比上次,这次更新主要引入了数据库作为持久层,用于支持不同历史对话的切换以及当前对话的上下文记录。同时,添加了 router 路由管理,采用 nodemon 实现热更新,并且在服务端实现了加载静态文件的功能。考虑到idea到项目的快速实现,没有花费过多时间精力进行前后端分离,后续项目会考虑使用 NextJS 框架。

二、参考

上下文硬盘缓存技术

DeepSeek API 提供了上下文硬盘缓存技术,并且对所有用户默认开启,用户无需修改代码即可使用。每一个用户请求都会触发硬盘缓存的构建。当后续请求与之前的请求在前缀上存在重复时,重复部分将从缓存中拉取,计入 “缓存命中”。 例如:

第一次请求
messages: [

{"role": "system", "content": "你是一位资深的财报分析师..."}

{"role": "user", "content": "<财报内容>\n\n请总结一下这份财报的关键信息。"}

]
第二次请求
messages: [
{"role": "system", "content": "你是一位资深的财报分析师..."}
{"role": "user", "content": "<财报内容>\n\n请分析一下这份财报的盈利情况。"}
]

在上述例子中,两次请求的相同前缀部分(即 system 消息 + user 消息中的 <财报内容>),在第二次请求时会被计入 “缓存命中”。这一技术大大提高了请求处理的效率,减少了与 AI 模型的重复交互,提升了用户体验。

JSON Output

deepseek官方的JSON规范化输出我在调试的过程中经常出现问题,因为有写AI生成内容为空就会跑出异常,所以我还是接续了上次的逻辑,在提示词里面做文章。

多轮对话

DeepSeek /chat/completions API 是一个“无状态” API,即服务端不记录用户请求的上下文,用户在每次请求时,需将之前所有对话历史拼接好后,传递给对话 API。

三、数据库(DB)设计

我选用了 prisma 作为 ORM 工具,因为真的快,只需编写好 model 并执行几个命令,即可完成整个配置。数据库的设计结构如下:

DB设计

四、项目结构

经典的 MVC 结构,基于 koa 服务搭建。项目结构如下:

excel-generator/ 
├── src/ 
│   ├── controller/     # 控制器层,负责处理请求和响应
│   ├── service/       # 服务层,封装业务逻辑
│   ├── config/        # 配置,存放各种配置信息
│   ├── view/          # 前端页面,提供用户交互界面
│   └── index.ts       # 入口文件,启动整个应用

五、提示词模板(Prompt Template)配置

提示词模板的配置对于与 AI 模型的交互至关重要。我们定义了两个主要的提示词模板:

export const EXCEL_GENERATOR_TEMPLATE = {
  SYSTEM_PROMPT: `你是一个经验丰富的 Excel 生成器。用户会给你一个生成 Excel 的需求,你需要根据需求返回一个 JSON 格式的模板。JSON 模板的结构如下:
  {
    "columns": [{"header": "列标题1", "key": "key1"}, ...],
    "data": [{"key1": "值1", "key2": "值2"}, ...]
  }
  只要纯json数据回应,不要包含其他任何内容`,

  EXCEL_TRANSFORM_PROMPT: `根据上传的Excel数据和用户需求生成新的数据结构,按照模板:
  JSON 模板的结构如下:
  {
    "columns": [{"header": "列标题1", "key": "key1"}, ...],
    "data": [{"key1": "值1", "key2": "值2"}, ...]
  }
  只要纯json数据回应,不要包含其他任何描述和说明的文字内容。`,
};

SYSTEM_PROMPT:这个提示词告诉 AI 模型,它的角色是一个 Excel 生成器,并且明确了返回的 JSON 格式模板结构。为了让模型生成的内容能够直接被我们的程序解析和使用,避免生成不必要的文本描述,方便后续处理。

EXCEL_TRANSFORM_PROMPT:用于在已有 Excel 数据的基础上,根据用户需求进行数据结构的转换。同样规定了返回的 JSON 格式,目的是确保 AI 模型的输出格式统一且符合我们的处理要求

六、核心函数功能

1. generateAIResponse 函数

  private async generateAIResponse(prompt: string, sessionId: number) {
    try {
      // 获取当前会话的历史消息
      const data = await prisma.message.findMany({
        where: {
          sessionId: sessionId,
        },
        orderBy: {
          createdAt: "asc",
        },
      });

      const messages: Array<ChatCompletionMessageParam> = [
        {
          role: "system",
          content: EXCEL_GENERATOR_TEMPLATE.SYSTEM_PROMPT,
        },
        ...data.map(
          (msg): ChatCompletionMessageParam => ({
            role: msg.role as "user" | "assistant" | "system",
            content: msg.content,
          })
        ),
        {
          role: "user",
          content: prompt,
        },
      ];

      const completion = await this.openai.chat.completions.create({
        model: "deepseek-chat",
        messages,
        temperature: 0,
        // response_format: { type: "json_object" },
      });

      const responseContent = completion.choices[0]?.message?.content?.trim();
      if (!responseContent) {
        throw new Error("AI 响应内容为空");
      }

      return responseContent;
    } catch (error) {
      console.error("生成 AI 响应失败:", error);
      throw new Error("生成回复失败,请稍后重试");
    }
  }

功能:这个函数用于生成 AI 响应。首先从数据库中获取指定会话 ID(sessionId)的所有历史消息,然后将系统提示词、历史消息以及当前用户输入的提示词(prompt)组合成一个完整的消息数组,发送给 AI 模型(deepseek-chat)进行处理。最后,从 AI 模型的响应中提取内容并返回。

2. transformExcelData 函数

  async transformExcelData(originalData: any, prompt: string) {
    try {
      const messages: Array<ChatCompletionMessageParam> = [
        {
          role: "system",
          content: EXCEL_GENERATOR_TEMPLATE.EXCEL_TRANSFORM_PROMPT,
        },
        {
          role: "user",
          content: `原始数据: ${JSON.stringify(
            originalData
          )}\n用户需求: ${prompt}`,
        },
      ];

      const completion = await this.openai.chat.completions.create({
        model: "deepseek-chat",
        messages: messages,
        temperature: 0,
      });

      if (!completion.choices[0]?.message?.content) {
        throw new Error("AI响应内容为空");
      }

      const cleanContent = this.cleanJsonString(
        completion.choices[0].message.content
      );

      return JSON.parse(cleanContent);
    } catch (error) {
      console.error("转换Excel数据失败:", error);
      throw new Error("转换Excel数据失败,请稍后重试");
    }
  }

功能:它的作用是根据上传的原始 Excel 数据(originalData)和用户需求提示词(prompt),通过调用 AI 模型(deepseek-chat)生成新的数据结构。它将系统提示词和包含原始数据与用户需求的消息发送给 AI 模型,获取响应后,对响应内容进行清理和解析,最终返回解析后的新数据结构。

七、页面UI交互设计

我参照了GPT和Gemini的经典左右两栏式设计和页面风格,回复用table view展示,引入之前的xlsx库cdn来做表格内容下载。

  1. 页面结构

app-container/
├── sidebar/ # 左侧会话列表
│ ├── new-chat-button # 新建会话按钮

│ └── chat-list # 会话列表
└── main-content/ # 主要内容区 ├── chat-history # 聊天记录
└── input-container # 输入区域
└── toolbar # 工具栏(上传按钮、输入框、发送按钮)

  1. 核心功能流程
  • 初始化流程
initializeChat() {
  1. 获取用户会话列表 GET /sessions
  2. 渲染会话列表 renderSessions()
  3. 获取当前会话消息 GET /sessions/${id}/messages
  4. 渲染消息列表 renderMessages()
}
  • 发送消息流程
sendMessage() {
  if (有上传文件) {
    1. 上传文件 POST /upload-excel
    2. 保存用户消息 POST /messages
    3. 保存AI响应 POST /messages
    4. 更新UI addMessage()
  } else {
    1. 发送普通消息 POST /messages
    2. 更新UI addMessage()
  }
}

3. 页面展示

  • 对话session切换:

  • 上传文件:

  • 回复展示:

  • 下载验收:

summarize

这个项目大部分的时间都在调输入输出,在数据转换上花的功夫比较多,不过还是一个晚上搞定了,因为有ai工作流的加持,很多基本重复的编码工作都被省去了,这个项目之后可能也会继续增加别的功能并做重构,但核心功能都已经差不多了。我之后可能会做工作内容相关的生成思维导图,或者其他AIGC的试验和玩具,欢迎一起交流哈~

微信公众号:冻柠葡萄呗