剧本聊天 Agent

这个作业属于哪个课程 Class56-Grade2024-CS
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/16760
这个作业的目标 完成可调用工具的 Agent
学号 3124004056黄凯祺
github链接 https://github.com/CageHuang1220/AIScriptAgent

剧本聊天 Agent 说明文档

1. 需求描述

本项目实现一个基于 Spring AI 的轻量级剧本聊天 Agent,满足作业中“智能体必须能:自然语言对话(能说)、执行操作或调用工具(会做)”两项能力要求。

具体需求如下:

  1. 剧本上传:用户上传一个 TXT 格式的剧本文件,系统解析文件名、标题、行数、字数、角色列表、幕/场列表等基础信息。
  2. 自然语言对话:用户能够围绕该剧本与 Agent 自由对话,Agent 可以回答剧情问题,也可以扮演剧本中的角色与用户对戏。
  3. 工具调用:Agent 在需要精确信息时,能够主动调用工具检索剧本原文,而不是仅凭生成内容“凭感觉”回答。工具包括剧本概览、角色列表、关键词检索、按行号读取。
  4. 历史记录:多轮对话需要具备上下文记忆能力;历史记录 Advisor 的缓存使用 Java Map 实现。
  5. 轻量化:尽可能只使用 Java 与 Spring 生态,不引入 SQL 数据库、向量数据库、消息队列等中间件;剧本与历史记录均保存在进程内存中。
  6. 前端:提供一个可直接使用的网页界面,用于上传剧本、查看剧本信息、进行对话和清空历史。

2. 业务流描述

整体业务流如下:

  1. 上传剧本

    • 用户在网页左侧选择或拖拽一个 TXT 文件。
    • 前端通过 POST /api/scripts/upload 将文件上传到后端。
    • 后端读取文本,解析出标题、角色、幕/场等元数据,生成一个唯一的 sessionId,并将剧本保存在内存的 ScriptStore 中。
  2. 发起对话

    • 前端将 sessionId 和用户输入发送到 POST /api/chat。
    • 后端组装系统提示词:把 Agent 人设、工作规则以及“剧本全文”注入 Prompt(纯 Prompt 方案,不进行向量化检索)。
    • MessageChatMemoryAdvisor 从基于 Map 的历史缓存中取出当前会话的历史消息,拼接到本次 Prompt 中。
    • 模型根据用户问题决定是直接回答,还是调用工具。
  3. 工具调用

    • 当问题涉及“有哪些角色”“某句台词在哪里”等精确信息时,模型发起 function calling。
    • Spring AI 将工具调用分发给 ScriptTools,并通过 ToolContext 把 sessionId 传给工具。
    • 工具从 ScriptStore 中取出对应剧本进行检索,返回结果;模型再根据工具结果生成最终回答。
  4. 记忆回写

    • 本轮对话结束后,Advisor 将用户消息与助手回复写入 MapChatMemory(系统提示不缓存,避免剧本全文在多轮中重复堆积)。
    • 下一轮对话时自动携带历史上下文,实现多轮记忆。
  5. 会话管理

    • “清空对话”只清除历史缓存,保留剧本。
    • “更换剧本”删除整个会话(剧本与历史),回到初始状态。

3. 实现说明

3.1 技术选型

部分 选型
后端框架 Spring Boot 3.4.5
AI 框架 Spring AI 1.0.0,spring-ai-starter-model-openai
模型接口 OpenAI 兼容接口,默认 DeepSeek,可切换 OpenAI / 通义千问
历史缓存 自定义 MapChatMemory,底层为 ConcurrentHashMap
剧本存储 自定义 ScriptStore,底层为 ConcurrentHashMap
前端 原生 HTML + CSS + JavaScript,由 Spring Boot 静态资源托管

3.2 项目结构

src/main/java/com/example/scriptagent/
├── ScriptAgentApplication.java      # 启动类
├── config/ChatConfig.java           # ChatClient 与 Advisor Bean 装配
├── memory/MapChatMemory.java        # 基于 Map 的历史记录缓存
├── script/
│   ├── ScriptDocument.java          # 剧本解析与元数据
│   ├── ScriptSession.java           # 会话
│   └── ScriptStore.java             # 内存剧本仓库
├── service/ScriptChatService.java   # 对话编排:Prompt + 记忆 + 工具
├── tool/ScriptTools.java            # @Tool 工具集
└── web/
    ├── ChatController.java
    ├── ScriptController.java
    ├── GlobalExceptionHandler.java
    └── dto/                         # 请求响应对象

3.3 核心实现

历史记录 Advisor 使用 Map 缓存

MapChatMemory 实现了 Spring AI 的 ChatMemory 接口,内部只用一个 ConcurrentHashMap<String, List<Message>> 保存各会话历史,不依赖任何数据库。

@Component
public class MapChatMemory implements ChatMemory {
    private final Map<String, List<Message>> conversations = new ConcurrentHashMap<>();
    // add / get / clear 三个方法围绕 conversations 实现
}

ChatConfig 将它与 Spring AI 官方的 MessageChatMemoryAdvisor 绑定:

MessageChatMemoryAdvisor.builder(chatMemory).build();

每次请求通过 .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId)) 指定当前会话,Advisor 就按会话从 Map 中读取和写入历史。

纯 Prompt 注入剧本

ScriptChatService 将剧本全文写入系统消息(超过 script-agent.max-prompt-chars 时截断,并提示模型用工具检索其余内容)。整个流程不涉及向量化和 RAG,符合“纯 Prompt”的要求。

工具调用(会做)

ScriptTools 使用 Spring AI 的 @Tool 注解声明工具,共 4 个:

工具 作用
scriptOverview 返回剧本概览(标题、行数、字数、角色、幕/场)
listCharacters 列出所有角色
searchScript 按关键词检索台词并返回行号
getLine 按行号读取一行原文

工具通过 ToolContext 获取 sessionId,从而定位到当前会话的剧本,避免让模型直接传递会话 ID:

@Tool(description = "...")
public String searchScript(@ToolParam(description = "...") String keyword, ToolContext toolContext) {
    Object sessionId = toolContext.getContext().get("sessionId");
    // 从 ScriptStore 中取剧本并检索
}

3.4 接口列表

方法 路径 说明
POST /api/scripts/upload 上传 TXT 剧本
GET /api/scripts/{sessionId} 查询剧本信息
POST /api/chat 对话
DELETE /api/sessions/{sessionId}/history 清空历史
DELETE /api/sessions/{sessionId} 删除会话

4. 使用示例(功能演示)

4.1 启动

$env:JAVA_HOME = "D:\Program Files\jdk17"
$env:AI_API_KEY = "你的 API Key"
mvn spring-boot:run

访问 http://localhost:8080。

4.2 上传

在左侧上传区选择 sample-script.txt,界面展示:

  • 标题:《深夜食堂·一碗阳春面》
  • 行数、字数
  • 角色:老陈、小雨、阿婆
  • 幕/场:第一幕 雨夜、第二幕 打烊前

4.3 对话示例

问(自然语言对话)

这个剧本讲了什么?

答

这个剧本讲的是雨夜里的深夜食堂,老板老陈、赶稿的小雨和常客阿婆之间围绕一碗阳春面展开的温暖故事。

问(工具调用)

小雨说过哪些台词?

此时 Agent 会调用 searchScript 工具检索原文,再整理回答:

小雨的台词有:第 7 行“老板,来一碗阳春面,多放葱花。”、第 8 行“加班赶稿子,饿得前胸贴后背了。”、第 14 行“老板,你在这条街开了多少年了?”、第 17 行“那我以后常来,就当是给自己加个深夜食堂。”

问(角色扮演)

请扮演阿婆,和我打个招呼。

答

阿婆:孩子,又到饭点啦?再忙也要吃口热乎的,别把胃熬坏咯。

4.4 多轮记忆演示

用户:老陈开了多少年店?
Agent:二十年。
用户:那小雨呢,她为什么这么晚才来?
Agent:因为她加班赶稿子,饿得前胸贴后背了。

第二轮问答中,Agent 能结合上一轮“老陈开店二十年”的上下文继续对话,体现历史记录 Advisor 的作用。

5. 心得

这次作业让我真正理解了 Spring AI 中 ChatClient、Advisor 与 Tool 三者如何协作。最深的体会是:记忆不要为了“记住”而记住,像剧本全文这种每轮都要用的内容,放在系统 Prompt 里重新注入、而不写入历史缓存,才能避免上下文越滚越大。工具调用则让我看到“会做”的边界——模型只是表达“我想调用哪个工具、传什么参数”,真正的执行还是在 Java 代码里。

posted @ 2026-10-22 00:55  cagehuang  阅读(9)  评论(0)    收藏  举报