关于“技巧分享”板块

“三人行,必有我师。”欢迎来到社区技巧分享版块!

无论你是刚刚入门的新手,还是经验丰富的资深用户,我们都鼓励你随时在此分享那些让你效率倍增的技巧、或是曾让你绕路的“踩坑”经验。

我们欢迎各种类型的技巧分享,包括但不限于:

  • 推荐实用的Skills、提示词、Rules、MCP

  • 分享你的自定义智能体及效果

  • 上下文管理技巧

  • 模型选择及使用技巧

  • SOLO、Cue等核心功能技巧

  • 新手入门技巧与避坑指南

  • 不同角色(前端、后端、产品、设计师等)的TRAE使用经验分享

TRAE 技巧便利店:长期营业中

本板块设有固定栏目 「TRAE 技巧便利店」,我们会定期精选社区里的优质分享(创建话题时可选标签技巧便利店)进行公示,帮助更多人成长。

持续分享,共同进步。让我们汇聚点滴智慧,一起成为社区的“技巧达人”!

8 个赞

技巧便利店这个栏目太戳了!已经搬好小板凳,坐等各位大佬的Skills和提示词干货:sparkles:

6 个赞

日常做内部工具时,我习惯先用 **#Workspace引入项目上下文,让 AI 精准理解代码结构。搭建后台优先用SOLO 模式 **,一句话需求就能自动生成接口、页面与 CURD,大幅减少重复编码。报错直接选中日志右键Ask TRAE,一键定位并给出修复方案。我还会把团队规范封装成自定义 Skill,生成代码更贴合标准,几乎不用返工。另外用中文注释 +Tab 智能补全,写业务逻辑又快又规范。这些小技巧让我搭建后台效率提升明显,也能把更多精力放在业务优化上。

13 个赞

进入TRAE开始,像是一块海绵吸水,疯狂学习!期待学到更多!!

6 个赞

对于小白来说简直太友好

3 个赞

AI对前端工作覆盖率已经达到了80%,超级好用,希望未来能更好的贴合开发,期待~

4 个赞

我想用 SOLO 做一个系统工具

4 个赞

在已有系统上调整,重新画母版非常痛苦,TRAE在很大程度上保持了原有风格、关键字等,更贴近想要的内容,非常赞

4 个赞

报到:waving_hand:,大家好,小弟Trae新报到,主要辅助老师/教授进行 AI教研,做了些奇奇怪怪:japanese_acceptable_button::japanese_acceptable_button::growing_heart::growing_heart:的东西,各位大神多多:japanese_reserved_button:教! 这个是最近帮老师做的小东西,多多指教:right_arrow:- 哦! 【Skill 创作】🚀毕设不慌|一键搭建 3D 分子物理仿真?Trae SOLO 助你拿学分~
🎮 点击按钮进入【虚拟仿真|分子与光】体验

5 个赞

学习中 ,感谢分享

4 个赞

trae又升级了,记忆能力更强了,太好了,这样讨论完的需求不怕忘记了,相信这样在后面编码的时候会更好。但是还是要再观察一下,很可能记忆水平很快又不足了。如果字节跳动能再多给点免费使用次数,相信能让大家有更深刻的感受,从而更好的提高trae的水平。

4 个赞

现在的trae存在一个问题。我让它执行了一个任务之后,又让它执行一个任务。这两个任务是有逻辑关联的,它没有考虑到其中的逻辑。

markdown记录:

User

为了让agent有一定记忆能力,我希望将用户与agent最近的上下文对话保存在redis中,初步定为5轮对话。现在项目没有集成redis,我本地部署redis,端口都是默认的,没有设密码。可以先集成一下redis,保存对话的功能。请将redis集成到项目中

Assistant

现在创建 Redis 对话历史服务。

现在修改 ChatController,在调用 AI 时传入历史上下文。

Redis 已集成完成。

已完成的工作:

  1. [pom.xml](file:///d:/test/Agent/pom.xml) - 添加 spring-boot-starter-data-redis 依赖
  2. [application.yaml](file:///d:/test/Agent/src/main/resources/application.yaml) - 配置 Redis 连接(localhost:6379,无密码)
  3. [ChatHistoryService.java](file:///d:/test/Agent/src/main/java/com/agent/service/ChatHistoryService.java) - Redis 对话历史服务
    • 使用 Redis List 存储消息,格式:chat:history:{conversationId}
    • 自动限制列表长度,保留最近 5 轮(10条消息)
    • 提供保存、获取、清空历史的方法
  4. [ChatController.java](file:///d:/test/Agent/src/main/java/com/agent/controller/ChatController.java) - 集成 Redis 上下文
    • 调用 AI 前从 Redis 获取最近 5 轮对话
    • 将历史消息作为上下文传入 AI 模型
    • 流式完成后保存 AI 回复到 Redis

启动前确保:

  1. 本地 Redis 已启动(默认端口 6379)
  2. 刷新 Maven 依赖
  3. 重启应用

User

idea启动失败:Parameter 1 of constructor in com.agent.service.ChatHistoryService required a bean of type ‘com.fasterxml.jackson.databind.ObjectMapper’ that could not be found.

Assistant

ObjectMapper 在 Spring Boot 4.0 中可能没有自动注册为 Bean。修改为直接创建实例。

已修改。改为在构造函数中手动创建 ObjectMapper 实例,不再依赖 Spring 自动注入。重启即可。


User

可以看到,如果redis重启,那么对话中的信息就会清空。为了避免出现问题,讨论一下可以有什么防御措施,单纯讨论一下方案,暂时不要修改代码

Assistant

好的,单纯讨论方案。

问题分析

当前 Redis 数据是内存存储,Redis 重启后数据会丢失。这会导致:

  1. AI 失去上下文记忆(最近 5 轮对话)
  2. 用户感知到 AI “失忆”

防御方案

方案 1:Redis 持久化(推荐)

思路:让 Redis 自己把数据持久化到磁盘。

  • RDB 快照:定时保存数据快照到磁盘(如每 5 分钟)
  • AOF 日志:记录每次写操作,重启时重放恢复

优点:

  • 对应用层透明,不需要改代码
  • Redis 原生支持,配置简单
  • 性能影响小

缺点:

  • 可能丢失最后一次快照/写入之间的数据(RDB)
  • AOF 文件会随时间增长

配置示例(redis.conf):

appendonly yes
save 300 10

方案 2:数据库兜底

思路:MySQL 是持久化的,Redis 丢失时从 MySQL 恢复。

  • Redis 作为缓存层,优先读取
  • Redis 未命中或为空时,从 MySQL 加载最近 5 轮对话
  • 写入时同时写 Redis 和 MySQL(当前已实现)

优点:

  • 数据可靠性高,MySQL 是权威数据源
  • Redis 只作为性能优化层

缺点:

  • 需要修改代码逻辑
  • Redis 重启后首次请求会有 MySQL 查询延迟

方案 3:Redis + 本地缓存双写

思路:在应用层加一层本地缓存(如 Caffeine),作为 Redis 的后备。

  • 优先读本地缓存 → 再读 Redis → 最后读 MySQL
  • 写入时同时写三层

优点:

  • 即使 Redis 挂了,本地缓存仍可用
  • 响应速度最快

缺点:

  • 架构复杂度高
  • 多缓存一致性问题
  • 应用重启后本地缓存也会丢失

方案 4:Redis 集群/主从

思路:使用 Redis 主从复制或集群,避免单点故障。

  • 主节点故障时自动切换到从节点
  • 数据在多个节点间同步

优点:

  • 高可用,单节点故障不影响服务
  • 适合生产环境

缺点:

  • 部署复杂度高
  • 对本地开发环境过重

推荐方案

方案 1 + 方案 2 组合(最佳实践):

  1. 开启 Redis AOF 持久化:防止 Redis 重启丢失数据
  2. 数据库兜底:Redis 未命中时从 MySQL 加载历史

这样即使 Redis 完全崩溃,MySQL 中仍有完整数据,AI 可以从 MySQL 恢复上下文。

你觉得哪种方案更适合当前项目?


User

我认为方案二相对较好。但要考虑一种情况,用户开启了一个新对话,那么是不是就没有必要从Mysql中查询了,单纯讨论一下,暂时不要修改代码

Assistant

你说得对,这是一个很好的边界情况。

新对话场景分析

新对话 = Redis 无数据 + MySQL 也无数据

这种情况下:

  • Redis 查不到 → 正常(因为是新对话)
  • MySQL 查不到 → 也正常(确实没有历史)
  • 所以多查一次 MySQL 是浪费的

优化思路

方案 A:通过 conversationId 判断

  • 如果前端传了 conversationId → 说明是已有对话,Redis 为空时从 MySQL 加载
  • 如果前端没传 conversationId(后端新建的)→ 说明是新对话,不查 MySQL

优点:逻辑简单,通过 ID 就能判断
缺点:需要区分"新创建的 conversationId"和"已有的 conversationId"


方案 B:通过时间窗口判断

  • 新创建的会话,create_time 就是当前时间
  • 如果 create_time 和当前时间差 < 1 秒 → 认为是新对话,不查 MySQL
  • 否则 → 认为是已有对话,Redis 为空时查 MySQL

优点:不需要前端配合
缺点:时间判断不够优雅,有边界情况


方案 C:标记位

  • 新建会话时,在 Redis 中设置一个标记:chat:new:{conversationId} = true
  • 查询时先看有没有这个标记 → 有就不查 MySQL
  • 对话进行中(第一条消息后)删除标记

优点:语义清晰
缺点:多维护一个标记,增加复杂度


方案 D:最简单的方式

直接查 MySQL,但加条件:

// 只在 Redis 为空 且 conversationId 已存在时查询
if (redisHistory.isEmpty()) {
    // 从 MySQL 查询,但只查 create_time < 当前时间的记录
    // 或者查询消息数量,如果为 0 就直接返回空
    List<Message> dbHistory = conversationService.getMessages(conversationId);
    // 加载到 Redis
}

实际上,新对话查 MySQL 的开销很小:

  • 消息表有 conversation_id 索引
  • 新对话查出来是空结果集,几乎无开销
  • 而且这个查询只在 Redis 未命中时触发

我的建议

方案 A 最合理:

if (前端传了 conversationId) {
    // 已有对话
    从 Redis 获取历史
    if (Redis 为空) {
        从 MySQL 加载最近 5 轮
    }
} else {
    // 新对话
    不需要查 Redis,也不需要查 MySQL
    直接新建会话
}

逻辑清晰,性能最优。你觉得呢?


User

我也认为,通过前端传过来的conversationId判断是否查询很好。你认为还有什么需要考虑的吗?

Assistant

还需要考虑的场景

1. Redis 部分丢失(非全新对话)

场景:用户聊了 3 轮后 Redis 重启,数据丢失,但 MySQL 有完整记录。

  • 前端传了 conversationId → Redis 为空 → 从 MySQL 加载
  • 问题:MySQL 可能存了超过 5 轮的历史,需要只取最近 5 轮

2. 并发请求

场景:用户快速发送两条消息,第二条请求到达时第一条还没存到 Redis。

  • 两条请求都从 Redis 读到相同的历史
  • AI 回复可能基于相同的上下文
  • 影响:不大,因为两条消息是独立处理的

3. 用户切换历史对话

场景:用户先聊对话 A,然后切换到对话 B(已有历史)。

  • 对话 B 的 conversationId 已存在
  • Redis 中可能没有(过期/重启)
  • 需要从 MySQL 加载 → 这正是我们要解决的场景

4. MySQL 数据量大的情况

场景:一个对话聊了 100 轮,MySQL 有 200 条消息。

  • 从 MySQL 加载时需要 ORDER BY create_time DESC LIMIT 10
  • 需要确保 message 表有 conversation_id + create_time 的复合索引
  • 否则查询会变慢

5. Redis 故障降级

场景:Redis 完全挂了(不是重启,是服务不可用)。

  • 每次请求都走 MySQL 兜底
  • 问题:MySQL 压力增大,但对话功能仍可用
  • 需要加异常捕获,Redis 报错时不阻断主流程

6. 消息内容过长

场景:某条消息内容特别长(比如用户粘贴了一篇文章)。

  • Redis 存储没问题
  • 但传给 AI 模型的上下文 token 可能超限
  • 建议:除了限制轮数(5轮),还可以限制总 token 数或字符数

7. 多端同步

场景:用户同时在手机和电脑打开页面,两个设备用同一个 conversationId。

  • Redis 中的历史是共享的
  • 两边都能看到对方的消息(如果前端轮询或 WebSocket)
  • 当前架构支持,但前端需要配合

总结

核心逻辑已经很清晰了,主要补充点:

  1. MySQL 查询要加排序和限制:ORDER BY create_time DESC LIMIT 10
  2. Redis 异常要降级:Redis 挂了不阻断主流程,fallback 到 MySQL
  3. 索引优化:确保 message(conversation_id, create_time) 有索引

其他都是边缘情况,不影响主流程。你觉得可以开始实现了吗?


User

我现在的想法是redis只保存最近5轮对话,多余的对话移除redis。为了防止redis重启,判断一下redis中是否有信息,如果没有就从mysql中找最近5轮的对话。如果是新话题,就不去找。分析一下这样做性能怎么样?

Assistant

性能分析

正常情况(Redis 有数据)

每次请求:
1. 读 Redis → ~1ms
2. 写 Redis(2次)→ ~2ms
3. 写 MySQL(2次)→ ~10ms
总延迟:~13ms

性能很好,Redis 操作是内存级别,MySQL 只负责持久化。


Redis 重启后首次请求

每次请求:
1. 读 Redis → 空(~1ms)
2. 读 MySQL(LIMIT 10)→ ~5ms(有索引)
3. 写 Redis(2次)→ ~2ms
4. 写 MySQL(2次)→ ~10ms
总延迟:~18ms

性能依然很好,MySQL 查询有索引,只取 10 条,开销很小。


新话题

每次请求:
1. 新建会话 → ~5ms(MySQL INSERT)
2. 不读 Redis,不读 MySQL
3. 写 Redis(2次)→ ~2ms
4. 写 MySQL(2次)→ ~10ms
总延迟:~17ms

最优,避免了无意义的 Redis/MySQL 读取。


关键性能指标

场景 延迟 Redis 操作 MySQL 操作
正常对话 ~13ms 读1写2 写2
Redis重启后 ~18ms 读1写2 读1写2
新话题 ~17ms 写2 写2

潜在瓶颈

1. MySQL 写入是同步的

当前代码中,conversationService.addMessage() 是同步写入 MySQL。如果 MySQL 慢,会阻塞流式响应。

影响:流式内容已经返回给用户了,但 doOnComplete 中的 MySQL 写入可能还没完成。

建议:MySQL 写入可以异步化(不影响用户体验)。


2. Redis 的 trim 操作

stringRedisTemplate.opsForList().trim(key, -10, -1);

每次写入都执行 trim,但 Redis List 的 trim 是 O(N) 操作(N=列表长度)。

影响:列表只有 10 个元素,O(10) ≈ O(1),几乎无影响。


3. MySQL 查询的索引

SELECT * FROM message 
WHERE conversation_id = ? 
ORDER BY create_time DESC 
LIMIT 10;

必须有索引:INDEX idx_conversation_id (conversation_id)

当前 schema.sql 已经有了,没问题。


结论

性能非常好,理由:

  1. :white_check_mark: Redis 作为主缓存,99% 的请求只走 Redis
  2. :white_check_mark: MySQL 只在 Redis 重启时兜底,且查询有索引
  3. :white_check_mark: 新话题避免无意义查询
  4. :white_check_mark: 数据量小(最多 10 条),内存和磁盘开销都低

唯一可优化点:MySQL 写入可以异步化,但当前同步写入也不会成为瓶颈(10ms 级别)。

可以开始实现了。


User

好的,那么你认为,按照这个方案需要修改哪些代码,我审核一下

Assistant

需要修改的代码清单

1. ConversationMapper.java - 新增查询方法

/**
 * 查询会话的最近消息(用于恢复Redis)
 * @param conversationId 会话ID
 * @param limit 限制数量
 * @return 消息列表,按时间倒序
 */
List<Message> getRecentMessages(@Param("conversationId") Long conversationId, 
                                 @Param("limit") int limit);

2. ConversationMapper.xml - 新增 SQL

<select id="getRecentMessages" resultType="Message">
    SELECT id, conversation_id, role, content, create_time
    FROM message
    WHERE conversation_id = #{conversationId}
    ORDER BY create_time DESC
    LIMIT #{limit}
</select>

3. ConversationService.java - 新增服务方法

/**
 * 获取会话的最近消息(用于Redis恢复)
 */
public List<Message> getRecentMessages(Long conversationId, int limit) {
    return conversationMapper.getRecentMessages(conversationId, limit);
}

4. ChatHistoryService.java - 修改核心逻辑

新增方法:

/**
 * 获取对话历史(带MySQL兜底)
 * @param conversationId 会话ID
 * @param isNewConversation 是否新对话
 * @return 对话历史
 */
public List<Map<String, String>> getHistoryWithFallback(Long conversationId, boolean isNewConversation)

逻辑:

if (isNewConversation) {
    return 空列表
} else {
    从 Redis 读取
    if (Redis 为空) {
        从 MySQL 读取最近10条
        加载到 Redis
        返回
    }
    返回 Redis 数据
}

5. ChatController.java - 修改调用逻辑

修改点:

// 判断是否新对话
boolean isNewConversation = (前端未传 conversationId);

// 获取历史(带兜底)
List<Map<String, String>> history = chatHistoryService.getHistoryWithFallback(
    conversationId, isNewConversation
);

// 后续逻辑不变...

总结

文件 修改类型 说明
ConversationMapper.java 新增方法 查询最近消息
ConversationMapper.xml 新增 SQL 倒序查询 LIMIT 10
ConversationService.java 新增方法 封装 Mapper 调用
ChatHistoryService.java 核心修改 增加 MySQL 兜底逻辑
ChatController.java 小修改 传入 isNewConversation 标志

不涉及:前端代码、数据库表结构、配置文件。

请审核。


User

好的,方案审核通过,请帮我生成这些代码修改并应用到对应的文件中

Assistant

所有代码已应用完成。

修改汇总:

文件 修改内容
[ConversationMapper.java](file:///d:/test/Agent/src/main/java/com/agent/mapper/ConversationMapper.java) 新增 getRecentMessages 方法
[ConversationMapper.xml](file:///d:/test/Agent/src/main/resources/mapper/ConversationMapper.xml) 新增 SQL:倒序查询最近消息 LIMIT
[ConversationService.java](file:///d:/test/Agent/src/main/java/com/agent/service/ConversationService.java) 新增 getRecentMessages 封装方法
[ChatHistoryService.java](file:///d:/test/Agent/src/main/java/com/agent/service/ChatHistoryService.java) 新增 getHistoryWithFallback 方法,注入 ConversationService
[ChatController.java](file:///d:/test/Agent/src/main/java/com/agent/controller/ChatController.java) 使用 getHistoryWithFallback 替代 getRecentHistory,传入 isNewConversation 标志

重启应用即可生效。


User

我在测试时发现一个问题,我先聊了两句后,关闭redis并重启redis。然后再进行对话发现agent忘记了刚才的对话。我查看了一下对话接口的逻辑,好像有点问题。现在发送对话后先将对话保存到redis中然后再查询redis中是否有对话,没有对话再从mysql中导入对话。这逻辑有问题啊。先把这句对话保存到redis中肯定有对话啊。你看一下,确认一下是不是有这个问题,先不要修改代码,只确认问题

Assistant

你说得完全正确。我确认了问题所在。

当前逻辑顺序(有问题):

// 1. 先保存用户消息到 Redis
chatHistoryService.saveUserMessage(conversationId, prompt);

// 2. 然后查询历史(此时 Redis 已经有数据了,永远不会走 MySQL 兜底)
List<Map<String, String>> history = chatHistoryService.getHistoryWithFallback(
        conversationId, isNewConversation
);

问题:

  • 第 1 步已经把当前消息写入 Redis
  • 第 2 步查询时 Redis 永远不为空
  • 所以 getHistoryWithFallback 中的 MySQL 兜底逻辑永远不会执行
  • Redis 重启后,历史数据丢失,但新消息写入后 Redis 只有这一条,之前的历史找不回来了

正确顺序应该是:

  1. 先查询历史(带 MySQL 兜底)
  2. 再保存当前消息到 Redis

确认无误,需要修改。


User

好的,请帮我修正代码逻辑,先查询历史再保存消息

Assistant

等一下,还有一个问题。当前消息先保存到 MySQL,然后查询历史时 MySQL 会包含刚保存的当前消息,导致 AI 收到重复的当前消息。

需要把 MySQL 保存也移到查询历史之后。

已修正。现在的正确顺序是:

  1. 查询历史(带 MySQL 兜底)→ 在保存当前消息之前
  2. 保存当前消息到 MySQL
  3. 保存当前消息到 Redis
  4. 调用 AI

这样 Redis 重启后,查询历史时 Redis 为空,会从 MySQL 恢复之前的对话。重启后再测试应该没问题了。


4 个赞

(更新了 各位彦祖[庆祝],自己搭建了一个服务器 欢迎大家体验[爱心])

4 个赞

持续分享,共同进步。让我们一起进步!

3 个赞

tare社区应该是国内aicoding社区做的最好的了吧 :melting_face:

3 个赞

真的被 Trae Work 积分消耗逻辑整懵了:face_with_steam_from_nose:

之前一直优先扣老用户福利积分,今天毫无预兆直接改成先消耗每日签到积分。 关键消耗顺序还不按到期时间走!快过期的老用户福利积分放着不动,先把签到攒下的积分吃掉。

眼看老用户福利马上就要到期清零,系统却优先用长期有效的签到积分,这逻辑实在让人费解。 没有任何通知,规则悄咪咪变更,攒积分的体验大打折扣。

2 个赞