package engine // norm_error_content_strip.go -- 错误内容剥离. // // 精妙之处(CLEVER): 如果之前发了一个太大的 PDF/图片导致 API 报错, // 下次 API 调用时自动剥离那个内容块.否则同样的错误会每次调用都重复. // 这是防止"一张坏图毁掉整个会话"的关键机制. // // 实现:扫描消息历史中的 API 错误消息,向前回溯找到导致错误的内容块, // 标记为需要剥离.后续 API 调用时跳过这些块. // // 升华改进(ELEVATED): 不硬编码错误类型,而是通过错误消息内容匹配. // 仓储场景可能有自己的错误(如传感器数据格式错误), // 同样的机制自动适用. // 替代方案:硬编码 PDF/图片错误类型(原始设计,只覆盖已知错误). // // 来源:新增步骤,早期方案无此功能但需要此机制防止会话因单个坏内容块而卡死. // 跨场景通用:任何包含二进制内容(图片,文档,传感器数据)的场景. import ( "strings" "git.flytoex.net/yuanwei/flyto-agent/pkg/query" ) // DefaultErrorPatterns 默认错误模式 → 需要剥离的内容块类型. // // key: 错误消息中的关键词(小写) // value: 匹配时需要剥离的内容块类型列表 var DefaultErrorPatterns = map[string][]query.ContentType{ "too large": {query.ContentDocument, query.ContentImage}, "password protected": {query.ContentDocument}, "invalid format": {query.ContentDocument}, "request too large": {query.ContentDocument, query.ContentImage}, } // ErrorContentStripper 根据错误模式自动剥离导致错误的内容块. type ErrorContentStripper struct { // Patterns 错误模式映射.如果为 nil,使用 DefaultErrorPatterns. Patterns map[string][]query.ContentType } func (s *ErrorContentStripper) Name() string { return "error_content_strip" } func (s *ErrorContentStripper) Priority() int { return 15 } func (s *ErrorContentStripper) patterns() map[string][]query.ContentType { if s.Patterns != nil { return s.Patterns } return DefaultErrorPatterns } func (s *ErrorContentStripper) Normalize(messages []query.Message) []query.Message { if len(messages) == 0 { return messages } patterns := s.patterns() if len(patterns) == 0 { return messages } // 第一遍:找到所有错误消息,确定需要剥离的内容块类型集合 stripTypes := s.findStripTypes(messages, patterns) if len(stripTypes) == 0 { return messages } // 第二遍:剥离匹配类型的内容块 result := make([]query.Message, 0, len(messages)) for _, msg := range messages { if len(msg.Content) == 0 { continue // 空消息直接跳过(EmptyMessageFilter 也会处理) } var filteredContent []query.Content for _, c := range msg.Content { if stripTypes[c.Type] { continue } filteredContent = append(filteredContent, c) } // 即使内容被全部剥离,也保留消息(用文本替代) if len(filteredContent) == 0 { filteredContent = []query.Content{ {Type: query.ContentText, Text: "[content removed due to previous API error]"}, } } result = append(result, query.Message{ Role: msg.Role, Content: filteredContent, Time: msg.Time, Metadata: msg.Metadata, }) } return result } // findStripTypes 扫描错误消息,返回需要剥离的内容块类型集合. // // 精妙之处(CLEVER): 只在 assistant 消息的 text 块中查找错误关键词. // API 错误通常以 assistant 文本消息的形式出现在历史中 // (由 engine 将错误信息插入会话). func (s *ErrorContentStripper) findStripTypes( messages []query.Message, patterns map[string][]query.ContentType, ) map[query.ContentType]bool { stripTypes := make(map[query.ContentType]bool) for _, msg := range messages { for _, c := range msg.Content { if c.Type != query.ContentText || !c.IsError { continue } lower := strings.ToLower(c.Text) for pattern, types := range patterns { if strings.Contains(lower, pattern) { for _, t := range types { stripTypes[t] = true } } } } } return stripTypes }