Files
smartmate/backend/services/agent/model/conversation_context.go
Losita d7184b776b Version: 0.9.75.dev.260505
后端:
1.收口阶段 6 agent 结构迁移,将 newAgent 内核与 agentsvc 编排层迁入 services/agent
- 切换 Agent 启动装配与 HTTP handler 直连 agent sv,移除旧 service agent bridge
- 补齐 Agent 对 memory、task、task-class、schedule 的 RPC 适配与契约字段
- 扩展 schedule、task、task-class RPC/contract 支撑 Agent 查询、写入与 provider 切流
- 更新迁移文档、README 与相关注释,明确 agent 当前切流点和剩余 memory 迁移面
2026-05-05 16:00:57 +08:00

213 lines
6.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package model
import (
"strings"
"github.com/cloudwego/eino/schema"
)
// ConversationContext 承载"本轮要喂给模型的输入材料"。
//
// 职责边界:
// 1. 负责保存 system prompt、对话历史、置顶注入块、工具 schema 摘要;
// 2. 负责提供最小必要的安全访问方法,避免 node / prompt 层直接散落切片操作;
// 3. 不负责流程推进phase / round / current step 仍归 CommonState 管;
// 4. 不负责真正的 prompt 组装,消息如何拼接仍应放在 prompt 层处理。
type ConversationContext struct {
SystemPrompt string `json:"system_prompt"`
History []*schema.Message `json:"history"`
PinnedBlocks []ContextBlock `json:"pinned_blocks"`
ToolSchemas []ToolSchemaContext `json:"-"` // 每次请求由 Service 层重新注入,不持久化
}
// ContextBlock 表示一段可被"置顶注入"的自然语言上下文。
//
// 设计目的:
// 1. Key 用于让调用方按语义覆盖,例如 current_plan / current_step / execution_rule
// 2. Title 用于 prompt 层后续决定是否渲染成小标题;
// 3. Content 存真正的自然语言内容,保持你当前"plan 用自然语言表达"的思路。
type ContextBlock struct {
Key string `json:"key"`
Title string `json:"title"`
Content string `json:"content"`
}
// ToolSchemaContext 是工具描述的轻量快照。
//
// 职责边界:
// 1. 这里只保留 prompt 注入真正需要的摘要信息;
// 2. SchemaText 约定存"已经整理好的自然语言 / JSON schema 摘要"
// 3. 不直接耦合具体 tool registry 里的复杂结构,避免 model 层反向依赖工具实现。
type ToolSchemaContext struct {
Name string `json:"name"`
Desc string `json:"desc"`
SchemaText string `json:"schema_text"`
}
// NewConversationContext 创建最小上下文容器。
func NewConversationContext(systemPrompt string) *ConversationContext {
return &ConversationContext{
SystemPrompt: strings.TrimSpace(systemPrompt),
}
}
// SetSystemPrompt 更新系统提示词。
func (c *ConversationContext) SetSystemPrompt(systemPrompt string) {
if c == nil {
return
}
c.SystemPrompt = strings.TrimSpace(systemPrompt)
}
// ReplaceHistory 整体替换对话历史。
//
// 职责边界:
// 1. 负责把"会话快照恢复"这类场景需要的一次性覆盖入口收口到这里;
// 2. 只复制消息切片本身,避免调用方后续 append 污染同一底层数组;
// 3. 不深拷贝每个 message 指针,消息对象本身仍默认由上游按只读方式使用。
func (c *ConversationContext) ReplaceHistory(history []*schema.Message) {
if c == nil {
return
}
c.History = cloneMessageSlice(history)
}
// AppendHistory 追加对话历史。
//
// 处理策略:
// 1. 跳过 nil message避免后续 prompt 拼装时出现空指针;
// 2. 仅负责顺序追加,不做去重,不做裁剪;
// 3. 历史裁剪策略属于后续 prompt / memory 层能力,此处先不下沉。
func (c *ConversationContext) AppendHistory(messages ...*schema.Message) {
if c == nil || len(messages) == 0 {
return
}
for _, msg := range messages {
if msg == nil {
continue
}
c.History = append(c.History, msg)
}
}
// HistorySnapshot 返回历史消息的浅拷贝切片。
func (c *ConversationContext) HistorySnapshot() []*schema.Message {
if c == nil {
return nil
}
return cloneMessageSlice(c.History)
}
// UpsertPinnedBlock 按 Key 写入或覆盖一段置顶上下文。
//
// 步骤说明:
// 1. Key 为空时直接忽略,因为后续无法做稳定覆盖;
// 2. 若已存在同 Key block则原位覆盖保证"当前 plan / 当前步骤"这类上下文始终只有一份;
// 3. 若不存在,则追加到末尾,至于渲染顺序由 prompt 层统一决定;
// 4. 此处不自动裁剪旧内容,避免 model 层擅自丢信息。
func (c *ConversationContext) UpsertPinnedBlock(block ContextBlock) {
if c == nil {
return
}
key := strings.TrimSpace(block.Key)
if key == "" {
return
}
block.Key = key
block.Title = strings.TrimSpace(block.Title)
block.Content = strings.TrimSpace(block.Content)
for i := range c.PinnedBlocks {
if c.PinnedBlocks[i].Key == key {
c.PinnedBlocks[i] = block
return
}
}
c.PinnedBlocks = append(c.PinnedBlocks, block)
}
// RemovePinnedBlock 删除指定 Key 的置顶上下文。
func (c *ConversationContext) RemovePinnedBlock(key string) bool {
if c == nil {
return false
}
key = strings.TrimSpace(key)
if key == "" {
return false
}
for i := range c.PinnedBlocks {
if c.PinnedBlocks[i].Key != key {
continue
}
c.PinnedBlocks = append(c.PinnedBlocks[:i], c.PinnedBlocks[i+1:]...)
return true
}
return false
}
// PinnedBlockByKey 按 Key 读取指定的置顶上下文。
func (c *ConversationContext) PinnedBlockByKey(key string) (ContextBlock, bool) {
if c == nil {
return ContextBlock{}, false
}
key = strings.TrimSpace(key)
if key == "" {
return ContextBlock{}, false
}
for i := range c.PinnedBlocks {
if c.PinnedBlocks[i].Key == key {
return c.PinnedBlocks[i], true
}
}
return ContextBlock{}, false
}
// PinnedBlocksSnapshot 返回置顶上下文块的浅拷贝切片。
func (c *ConversationContext) PinnedBlocksSnapshot() []ContextBlock {
if c == nil {
return nil
}
result := make([]ContextBlock, len(c.PinnedBlocks))
copy(result, c.PinnedBlocks)
return result
}
// SetToolSchemas 整体替换工具 schema 摘要。
func (c *ConversationContext) SetToolSchemas(schemas []ToolSchemaContext) {
if c == nil {
return
}
c.ToolSchemas = cloneToolSchemaSlice(schemas)
}
// ToolSchemasSnapshot 返回工具 schema 摘要的浅拷贝切片。
func (c *ConversationContext) ToolSchemasSnapshot() []ToolSchemaContext {
if c == nil {
return nil
}
return cloneToolSchemaSlice(c.ToolSchemas)
}
func cloneMessageSlice(messages []*schema.Message) []*schema.Message {
if len(messages) == 0 {
return nil
}
result := make([]*schema.Message, len(messages))
copy(result, messages)
return result
}
func cloneToolSchemaSlice(schemas []ToolSchemaContext) []ToolSchemaContext {
if len(schemas) == 0 {
return nil
}
result := make([]ToolSchemaContext, len(schemas))
copy(result, schemas)
return result
}