feat(channel,runtime): 批次2 后端 — 消息级角色归属进上下文 + IM 私聊随主会话角色 + 渠道级绑定
按 design/22 §2/§3/§4.4/§7(3/4/5)。 - ①「消息级角色归属」进入模型上下文(批次 1 只做存储+DTO):历史消息的发送角色现在对模型可见,且**不把系统元数据塞进用户正文**(design/11 §10 红线) - ② IM owner 私聊镜像解析出的角色 = **主会话绑定的角色**(§7-3) - ③ 渠道级绑定:ChannelAccount 加 personaId(channels.json 读写/校验/脱敏沿用既有约定)+ 角色解析优先级 **会话 > 渠道 > 默认** + **群聊/陌生人强制默认角色**(§7-4);admin 渠道配置端点支持 personaId - Docs/api-client 版本 +1 - 验收:go build/vet/test 全绿 + linux/darwin 交叉编译
This commit is contained in:
1 parent
3732c538cc
commit
5969bba3bd
22 files changed
+1396
-23
No files matched your search
@@ -36,16 +36,7 @@ type asyncExtractor interface {
|
||||
// project memory is injected so no owner-sensitive memory leaks to a group or
|
||||
// stranger.
|
||||
func (l *Loop) injectMemory(ctx context.Context, messages []model.Message) []model.Message {
|
||||
personaID := l.personaFor(ctx)
|
||||
if l.retriever == nil || personaID == "" {
|
||||
return messages
|
||||
}
|
||||
env := sessionEnvFrom(ctx)
|
||||
block, err := l.retrieveMemoryBlock(ctx, env, personaID, queryFromMessages(messages))
|
||||
if err != nil {
|
||||
l.log.Warn("memory inject", "error", err, "env", env.ActorEnv)
|
||||
return messages
|
||||
}
|
||||
block := l.memoryBlock(ctx, messages)
|
||||
if block == "" {
|
||||
return messages
|
||||
}
|
||||
@@ -55,6 +46,25 @@ func (l *Loop) injectMemory(ctx context.Context, messages []model.Message) []mod
|
||||
return out
|
||||
}
|
||||
|
||||
// memoryBlock renders the round's memory block, or "" when there is nothing to
|
||||
// inject (no retriever/persona, a retrieval error, or no relevant memory). It is
|
||||
// split out from injectMemory because the assembly places other leading blocks
|
||||
// between the memory block and the conversation (the role context of design/22
|
||||
// §7-5), so it needs the block itself rather than a pre-prefixed slice.
|
||||
func (l *Loop) memoryBlock(ctx context.Context, messages []model.Message) string {
|
||||
personaID := l.personaFor(ctx)
|
||||
if l.retriever == nil || personaID == "" {
|
||||
return ""
|
||||
}
|
||||
env := sessionEnvFrom(ctx)
|
||||
block, err := l.retrieveMemoryBlock(ctx, env, personaID, queryFromMessages(messages))
|
||||
if err != nil {
|
||||
l.log.Warn("memory inject", "error", err, "env", env.ActorEnv)
|
||||
return ""
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
// retrieveMemoryBlock returns the memory injection block for env and the turn's
|
||||
// role. The owner-full env keeps the pre-existing Inject behavior. The restricted
|
||||
// env (group chat / stranger) injects only project-scope memory (人人可读, not
|
||||
@@ -105,9 +115,17 @@ func formatMemoryBlock(results []memory.Result, marker string) string {
|
||||
// re-injects the persona's relevant long-term memory — retrieval is re-run every
|
||||
// round from the current conversation, not cached from the first turn, so it
|
||||
// supports cross-round refresh — folds in the per-session working-memory summary
|
||||
// when present, and applies the optional TransformContext hook last so a caller
|
||||
// can project/filter what the model actually sees. It returns the input unchanged
|
||||
// when present, renders the role/speaker attribution of the conversation (design/22
|
||||
// §7-5), and applies the optional TransformContext hook last so a caller can
|
||||
// project/filter what the model actually sees. It returns the input unchanged
|
||||
// when nothing is configured.
|
||||
//
|
||||
// The existing order is preserved exactly: the leading blocks are the per-round
|
||||
// time, the working-memory summary and the memory block, in that order; the role
|
||||
// declaration sits right after them (immediately before the conversation it
|
||||
// describes) and the speaker boundary notes are inserted into the conversation
|
||||
// itself. A conversation with no speaker label renders no attribution at all, so
|
||||
// every single-role path keeps the prompt it had before multi-character support.
|
||||
func (l *Loop) assembleContext(ctx context.Context, messages []model.Message, round int) []model.Message {
|
||||
var leading []model.Message
|
||||
// Per-round "current time" so the model naturally knows the wall clock (not a
|
||||
@@ -122,11 +140,16 @@ func (l *Loop) assembleContext(ctx context.Context, messages []model.Message, ro
|
||||
leading = append(leading, model.TextMessage(model.RoleSystem, "工作记忆: "+s))
|
||||
}
|
||||
}
|
||||
leading = append(leading, l.injectMemory(ctx, messages)...)
|
||||
|
||||
out := leading
|
||||
if block := l.memoryBlock(ctx, messages); block != "" {
|
||||
leading = append(leading, model.TextMessage(model.RoleSystem, block))
|
||||
}
|
||||
decl, ok, view := l.attributeConversation(ctx, messages)
|
||||
if ok {
|
||||
leading = append(leading, decl)
|
||||
}
|
||||
out := append(leading, view...)
|
||||
if l.transformContext != nil {
|
||||
out = l.transformContext(ctx, leading, round)
|
||||
out = l.transformContext(ctx, out, round)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user