如何优雅的在页面上嵌入AI-Agent人工智能

前言

IDEA启动!大模型的title想必不用我多说了,多少公司想要搭上时代前言技术的快车,感受科技的魅力。现在大模型作为降本增效的强大工具,基本上公司大多人都想要部署开发一把,更多的想要利用到这些模型放到生产中来提高生产力。但是对于我们开发者来说,找到实际落地场景可以说是产品的活,我们需要思考如何高效维护AI这个模块,如何建立项目层级结构才能更好的解耦。正巧最近遇到了这个需求,来和大家分享项目搭建流程,此项目将运用到我个人开发的网页和网站上面,感兴趣的同学可以去体验一下,再来看看项目设计结构会更有心得。

项目顶层架构

首先AI这块应该在业务体系中应该较大的模块,AI落地应用的场景不限于AI客服问答,还可以应用在知识库管理、数据快速展示、ChatBI等一系列场景,但是目前我们的项目这块是用于Web应用上面的,所以采用一般也是JAVAWeb层级结构,那么我们就很好确定后端技术栈。

技术选型

  • Spring Boot: 作为项目的核心框架,用于快速构建 RESTful API 和管理依赖。

  • MyBatis-Plus: 用于数据库持久化操作,简化了 MyBatis 的使用,并提供了基本的 CRUD 方法。

  • AI SDK: 用于与AI 服务进行交互,处理 AI 模型的调用和返回结果。

  • Lombok : 用于减少样板代码(如 gettersetter、构造函数等)。

  • MySQL: 作为数据库,用于存储 AI 会话数据。

我这里调用的是阿里通义千问的模型,当然每个厂商的AI SDK都大差不差,调用规则基本都是一致的,通过查阅SDK的返回参数就能很轻松的调用:

核心流程

API 请求处理:

  • 前端或客户端通过 TongyiChatReqDto 提交用户的 prompt(问题)到后端。

  • 后端的 AliyunTongyiServicelmpl 使用阿里云的 dashscope API 调用,将用户的问题提交给 AI 模型。

API 响应处理:

  • dashscope API 返回 AI 的响应,封装在 AiResponse DTO 中。

  • 后端通过 AiResponseToPoConverterAiResponse 转换为 AiChatTongyiRespPo 实体类。

数据持久化:

  • 转换后的实体对象 AiChatTongyiRespPo 通过 AiChatTongyiRespRepository 保存到本地的 MySQL 数据库中。

  • 数据持久化包括存储 AI 的会话 ID、返回文本、结束状态、模型编号、输入和输出 tokens 等信息。

业务处理:

  • 后端返回封装好的 TongyiChatRespDto 响应对象,包含 AI 的回复文本,供前端展示。

顶层设计

首先应该有两个模块,一层为处理Web端数据回传,将用户在前端提交的文本数据处理,里面可以加入敏感词检测、不符对话逻辑过滤等一系列业务逻辑,然后把这些内容提交到AI交互模块;在数据落库方面还需要能够记录每次对话内容的交互数据,这部分的数据尽量偏业务方向,不需要记录AI返回的Tokens,状态等数据,只需要记录表层与用户交互的数据,比如对返回的文本点赞,评分等。

另一层为处理与AI数据交互,需要将我们处理过需要交互的数据提交到AI SDK的接口上,获取返回的数据,这部分数据也需要记录,因为后续我们可能会去做一些不同AI反馈效果,也可能会去进行大模型多模态的应用,所以要尽可能的低耦合,多划分层级,对后面后续维护项目很重要。之后还需要能够记录能够帮助我们调优AI性能的数据,比如每次传输的tokens数量,响应时间等数据。

项目实现

AI通信层

我们先来处理与AI通信交互这层功能:

api这一模块实现与AI接口的交互,也就是查阅相关SDK文档,找到AI返回的数据格式,获取到了数据之后记录到数据库之中,并且通过api传输给其他服务,确定层级有:

1. config 包

  • Configuration.java: 负责项目的配置管理,通常用于定义和加载一些项目启动时所需的配置类、Bean、服务或者第三方服务的连接配置。

  • Properties.java : 用于加载和存储配置文件(如 application.ymlapplication.properties)中的属性,主要用于配置 API 相关的信息,比如 apiKeychatAppId。通常使用 Spring 的 @ConfigurationProperties 注解,将配置文件中的内容映射为 Java 对象。

2. dal包

  • AiChatRespPo.java : 持久化对象(PO),用于映射到数据库表 ai_request。这个类的实例代表从数据库读取或保存到数据库的一条记录。它包含 AI 会话相关的数据,如 requestIdprompttextfinishReasonmodelIdinputTokensoutputTokens 等字段,通常与 MyBatis-Plus 或其他持久化框架一起使用。

3. domain包

  • AiResponse.java : 数据传输对象(DTO),用于封装从阿里云 Dashscope API 返回的 AI 响应数据,并在应用层之间传递。该类包含 AI 响应的相关信息,如 requestIdtextfinishReasonusage(模型使用信息)。DTO 的作用是将数据从服务层传递到表示层(或反之),不涉及业务逻辑或持久化操作。

4. domain 包

  • EvaluateEnum.java: 枚举类,用于定义用户对 AI 响应的评价状态,可能包含 "满意"、"不满意" 等选项。枚举类在代码中提供了一种类型安全的方式来表示固定的常量集。

5. exception 包

  • NetworkException.java: 自定义异常类,用于处理网络或 API 调用过程中的错误。自定义异常可以使异常处理更具语义化,并且能够在捕获异常时提供更多的上下文信息,比如错误码或详细的错误消息。

6. repository 包

  • AiChatRespRepository.java : 自定义的 Repository 接口,定义了保存 AiChatTongyiRespPo 实体对象的方法。这个接口负责抽象数据持久化操作,隐藏了底层的数据访问细节。实现该接口的类负责实际的数据操作逻辑,通常使用 MyBatis-Plus 或其他持久化技术。

7. repository.impl 包

  • AiChatRespRepositoryImpl.java : 该类实现了 AiChatTongyiRespRepository 接口,负责将 AiChatTongyiRespPo 实体对象保存到数据库中。它使用 MyBatis-Plus 或其他 ORM 框架来执行数据持久化操作,并管理数据库事务。

8. service包

  • AliyunTongyiServicelmpl.java : 这是核心的业务逻辑实现类,处理与阿里云 Dashscope AI 服务的交互。它通过阿里云 API 发起请求,获取 AI 模型的响应数据,然后将该数据通过转换器(AiResponseToPoConverter)转换为持久化对象,并保存到数据库中。同时,它还负责处理请求和响应的各种业务逻辑和异常情况。

基于上述内容我们可以来先设计DTO:

DTO

复制代码
public class AiResponse {
    /**
     * AI单条会话ID
     */
    private String requestId;
    /**
     * 提问内容
     */
    private String prompt;
    /**
     * AI返回的文本
     */
    private String text;
    /**
     * 完成原因
     */
    private String finishReason;
    /**
     * 模型信息
     */
    private List<ApplicationUsage.ModelUsage> usage;
​
    public AiResponse(String requestId,String prompt, String text, String finishReason, ApplicationUsage usage) {
        this.requestId = requestId;
        this.prompt = prompt;
        this.text = text;
        this.finishReason = finishReason;
        this.usage = usage != null ? usage.getModels() : null;
    }
}
​

这部分查阅SDK看返回数据格式,把这部分最简单先做了。然后设计数据落表:

数据表

复制代码
CREATE TABLE `ai_request` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键标识',
  `request_id` varchar(50) NOT NULL COMMENT '会话ID',
  `prompt` longtext COMMENT '提问内容',
  `text` longtext COMMENT '返回文本',
  `finishReason` varchar(5) NOT NULL COMMENT '结束状态',
  `model_code` varchar(50) NOT NULL COMMENT '模型编号',
  `input_tokens` bigint NOT NULL COMMENT '输入tokens',
  `output_tokens` bigint NOT NULL COMMENT '输出tokens',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='智能客服应答表';

有了表之后我们建立相应的转换和po层:

po

复制代码
public class AiChatTongyiRespPo {
    /**
     * 主键id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
​
    /**
     * 提问内容
     */
    @TableField(value = "prompt")
    private String prompt;
​
    /**
     * 返回编号
     */
    @TableField(value = "request_id")
    private String requestId;
​
    /**
     * 返回文本
     */
    @TableField(value = "text")
    private String text;
​
    /**
     * 结束状态
     */
    @TableField(value = "finish_reason")
    private String finishReason;
​
    /**
     * 模型编号
     */
    @TableField(value = "model_id")
    private String modelId;
​
    /**
     * 输入tokens
     */
    @TableField(value = "input_tokens")
    private String inputTokens;
​
    /**
     * 输出tokens
     */
    @TableField(value = "output_tokens")
    private String outputTokens;
}
​

再来写convert:

convert

复制代码
public static AiChatTongyiRespPo convert(AiResponse aiResponse) {
        AiChatTongyiRespPo po = new AiChatTongyiRespPo();
        ApplicationUsage.ModelUsage modelUsage = aiResponse.getUsage().get(0);
        // 将 DTO 中的数据映射到 PO 中
        po.setRequestId(aiResponse.getRequestId());
        po.setPrompt(aiResponse.getPrompt());
        po.setText(aiResponse.getText());
        po.setFinishReason(aiResponse.getFinishReason());
        po.setModelId(modelUsage.getModelId());
        po.setInputTokens(String.valueOf(modelUsage.getInputTokens()));
        po.setOutputTokens(String.valueOf(modelUsage.getOutputTokens()));
​
        return po;
    }

再来写实现层就好写了

service

复制代码
public TongyiChatRespDto chat(TongyiChatReqDto dto) {
        try {
            // 构建API调用参数
            ApplicationParam param = ApplicationParam.builder()
                    .apiKey(aliyunProperties.getTongyi().getApiKey())
                    .appId(aliyunProperties.getTongyi().getChatAppId())
                    .prompt(dto.getPrompt())
                    .build();
            Application application = new Application();
            ApplicationResult result = application.call(param);
            // 创建返回对象
            AiResponse response = new AiResponse(result.getRequestId(), dto.getPrompt(), result.getOutput().getText(), result.getOutput().getFinishReason(), result.getUsage());
            // 将 AiResponse 转换为实体类 AiChatTongyiRespPo
            AiChatTongyiRespPo po = AiResponseToPoConverter.convert(response);
            aiChatTongyiRespRepository.saveAiResponse(po);
            return new TongyiChatRespDto().setReply(result.getOutput().getText());

到现在写service就很好写了,通过层级划分(DTO、Service、Repository、PO 等),清晰分离了不同的职责,保证了代码的可维护性和扩展性。引入 Converter 模块将 DTO 转换为实体对象,简化了业务层的逻辑并实现了代码的复用。这一框架实现了从用户输入到 AI 模型响应再到数据持久化的全链路处理,下一章我们继续完善前端通信部分。

相关推荐
文心快码BaiduComate1 天前
百度云与光本位签署战略合作:用AI Agent 重构芯片研发流程
前端·人工智能·架构
风象南1 天前
Claude Code这个隐藏技能,让我告别PPT焦虑
人工智能·后端
Mintopia1 天前
OpenClaw 对软件行业产生的影响
人工智能
陈广亮1 天前
构建具有长期记忆的 AI Agent:从设计模式到生产实践
人工智能
会写代码的柯基犬1 天前
DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评
人工智能·llm
Mintopia1 天前
OpenClaw 是什么?为什么节后热度如此之高?
人工智能
爱可生开源社区1 天前
DBA 的未来?八位行业先锋的年度圆桌讨论
人工智能·dba
叁两1 天前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
前端付豪1 天前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain