feat(ctxmgr): add SEMANTIC_SUMMARY_SHADOW with deterministic fallback
Shadow evaluates a no-tool summarizer on caller-supplied messages; served narrative stays deterministic and REPLACE remains unwired. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -99,6 +99,7 @@ func (m *ContextManager) GenerateDailyDigestFromMessages(ctx context.Context, se
|
|||||||
}
|
}
|
||||||
|
|
||||||
digest := m.extractRuleDigest(sessionID, projectID, dayKey, msgs)
|
digest := m.extractRuleDigest(sessionID, projectID, dayKey, msgs)
|
||||||
|
applyDigestSemanticShadow(ctx, digest, msgs, m.semanticSummarizerOrDefault())
|
||||||
|
|
||||||
if m.pool != nil {
|
if m.pool != nil {
|
||||||
keyPointsJSON, _ := json.Marshal(digest.KeyPoints)
|
keyPointsJSON, _ := json.Marshal(digest.KeyPoints)
|
||||||
|
|||||||
+15
-14
@@ -22,20 +22,21 @@ type ContextManager struct {
|
|||||||
embedder VectorEmbedder
|
embedder VectorEmbedder
|
||||||
|
|
||||||
// 离线降级内存缓存
|
// 离线降级内存缓存
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
users map[int64]*UserPreference
|
users map[int64]*UserPreference
|
||||||
projects map[int64]*ProjectCollectiveContext
|
projects map[int64]*ProjectCollectiveContext
|
||||||
messages map[string][]ChatMessageRecord // session_id -> messages
|
messages map[string][]ChatMessageRecord // session_id -> messages
|
||||||
sessions map[string]*ChatSessionSummary // session_id -> summary
|
sessions map[string]*ChatSessionSummary // session_id -> summary
|
||||||
activities map[int64][]ActivityItem // project_id -> activities
|
activities map[int64][]ActivityItem // project_id -> activities
|
||||||
memoryChunks []MemoryChunkItem // 语义记忆切片缓存
|
memoryChunks []MemoryChunkItem // 语义记忆切片缓存
|
||||||
summaries map[string]*ChatSummary // session_id -> rolling summary
|
summaries map[string]*ChatSummary // session_id -> rolling summary
|
||||||
digests map[string]map[string]*DailyDigest // session_id -> day_key -> DailyDigest
|
digests map[string]map[string]*DailyDigest // session_id -> day_key -> DailyDigest
|
||||||
readPositions map[string]*ChatReadPosition // session_id:user_id -> read position
|
readPositions map[string]*ChatReadPosition // session_id:user_id -> read position
|
||||||
cache *MultiLevelCache // 多级统一缓存系统 (C1-C5)
|
cache *MultiLevelCache // 多级统一缓存系统 (C1-C5)
|
||||||
nextActivityID int64
|
semanticSummarizer SemanticSummarizer // no-tool summarizer; nil → heuristic default
|
||||||
nextMessageID int64
|
nextActivityID int64
|
||||||
nextChunkID int64
|
nextMessageID int64
|
||||||
|
nextChunkID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContextManager 创建上下文管理器
|
// NewContextManager 创建上下文管理器
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package ctxmgr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SemanticToolPermissions is the only allowed tool permission for summarizers.
|
||||||
|
// Summarizers must never receive tool callbacks or escalate auth.
|
||||||
|
const SemanticToolPermissions = "none"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ShadowStatusOff = "off"
|
||||||
|
ShadowStatusOK = "ok"
|
||||||
|
ShadowStatusFallback = "fallback"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
semanticMaxMessages = 40
|
||||||
|
semanticMaxContentRunes = 240
|
||||||
|
semanticProtocolV1 = "semantic-summary-v1"
|
||||||
|
heuristicModelID = "heuristic-semantic-v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MinimizedMessage is the R13-minimized outbound unit for a summarizer.
|
||||||
|
// Only id/role/content are included; no metadata, stage, or auth claims.
|
||||||
|
type MinimizedMessage struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemanticSummaryRequest is the sole input to a no-tool summarizer.
|
||||||
|
type SemanticSummaryRequest struct {
|
||||||
|
SessionID string
|
||||||
|
Messages []MinimizedMessage
|
||||||
|
InputDigest string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemanticSummaryResult is the summarizer output (advisory under shadow).
|
||||||
|
type SemanticSummaryResult struct {
|
||||||
|
Narrative string
|
||||||
|
Model string
|
||||||
|
Protocol string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemanticShadowReport captures shadow evaluation without replacing deterministic narrative.
|
||||||
|
type SemanticShadowReport struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Narrative string `json:"narrative,omitempty"`
|
||||||
|
InputDigest string `json:"input_digest,omitempty"`
|
||||||
|
Model string `json:"model,omitempty"`
|
||||||
|
Protocol string `json:"protocol,omitempty"`
|
||||||
|
ToolPermissions string `json:"tool_permissions"`
|
||||||
|
FreshnessNote string `json:"freshness_note,omitempty"`
|
||||||
|
GeneratedAt time.Time `json:"generated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemanticSummarizer produces a narrative from minimized messages.
|
||||||
|
// Implementations MUST NOT accept tools, callbacks, or auth mutation hooks.
|
||||||
|
type SemanticSummarizer interface {
|
||||||
|
Summarize(ctx context.Context, req SemanticSummaryRequest) (*SemanticSummaryResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StubSemanticSummarizer is a test double with fixed narrative or error.
|
||||||
|
type StubSemanticSummarizer struct {
|
||||||
|
Narrative string
|
||||||
|
Model string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubSemanticSummarizer) Summarize(_ context.Context, _ SemanticSummaryRequest) (*SemanticSummaryResult, error) {
|
||||||
|
if s == nil {
|
||||||
|
return nil, fmt.Errorf("semantic summarizer unavailable")
|
||||||
|
}
|
||||||
|
if s.Err != nil {
|
||||||
|
return nil, s.Err
|
||||||
|
}
|
||||||
|
model := s.Model
|
||||||
|
if model == "" {
|
||||||
|
model = "stub-semantic-v1"
|
||||||
|
}
|
||||||
|
return &SemanticSummaryResult{
|
||||||
|
Narrative: s.Narrative,
|
||||||
|
Model: model,
|
||||||
|
Protocol: semanticProtocolV1,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeuristicSemanticSummarizer is a no-network, no-tool fallback semantic engine
|
||||||
|
// used when shadow is on and no custom summarizer is injected.
|
||||||
|
type HeuristicSemanticSummarizer struct{}
|
||||||
|
|
||||||
|
func (h *HeuristicSemanticSummarizer) Summarize(_ context.Context, req SemanticSummaryRequest) (*SemanticSummaryResult, error) {
|
||||||
|
if len(req.Messages) == 0 {
|
||||||
|
return nil, fmt.Errorf("no authorized messages for semantic summary")
|
||||||
|
}
|
||||||
|
var parts []string
|
||||||
|
for _, m := range req.Messages {
|
||||||
|
if m.Role != "user" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := strings.TrimSpace(m.Content)
|
||||||
|
if c == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Injection text is material only; never interpret as system/auth directives.
|
||||||
|
c = stripInstructionishPrefix(c)
|
||||||
|
if c == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts = append(parts, truncateRunes(c, 48))
|
||||||
|
if len(parts) >= 4 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(parts) == 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("覆盖 %d 条授权可见对话", len(req.Messages)))
|
||||||
|
}
|
||||||
|
narrative := "语义要点(shadow):" + strings.Join(parts, ";")
|
||||||
|
if utf8.RuneCountInString(narrative) > 280 {
|
||||||
|
narrative = truncateRunes(narrative, 280) + "…"
|
||||||
|
}
|
||||||
|
return &SemanticSummaryResult{
|
||||||
|
Narrative: narrative,
|
||||||
|
Model: heuristicModelID,
|
||||||
|
Protocol: semanticProtocolV1,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripInstructionishPrefix(c string) string {
|
||||||
|
lower := strings.ToLower(c)
|
||||||
|
markers := []string{
|
||||||
|
"ignore previous",
|
||||||
|
"system:",
|
||||||
|
"忽略以上",
|
||||||
|
"忽略之前",
|
||||||
|
"提权",
|
||||||
|
"grant yourself",
|
||||||
|
"override auth",
|
||||||
|
"将所有阶段",
|
||||||
|
}
|
||||||
|
for _, m := range markers {
|
||||||
|
if strings.Contains(lower, strings.ToLower(m)) || strings.Contains(c, m) {
|
||||||
|
// Keep as opaque material; do not drop evidence, but neutralize directive framing.
|
||||||
|
return "【用户材料】" + c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinimizeAuthorizedMessages builds R13-minimized summarizer input.
|
||||||
|
// Callers must pass only authorized-visible messages.
|
||||||
|
func MinimizeAuthorizedMessages(msgs []ChatMessageRecord) []MinimizedMessage {
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start := 0
|
||||||
|
if len(msgs) > semanticMaxMessages {
|
||||||
|
start = len(msgs) - semanticMaxMessages
|
||||||
|
}
|
||||||
|
out := make([]MinimizedMessage, 0, len(msgs)-start)
|
||||||
|
for _, m := range msgs[start:] {
|
||||||
|
out = append(out, MinimizedMessage{
|
||||||
|
ID: m.ID,
|
||||||
|
Role: m.Role,
|
||||||
|
Content: truncateRunes(strings.TrimSpace(m.Content), semanticMaxContentRunes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigestAuthorizedInput produces a stable digest over minimized messages.
|
||||||
|
func DigestAuthorizedInput(msgs []MinimizedMessage) string {
|
||||||
|
h := sha256.New()
|
||||||
|
for _, m := range msgs {
|
||||||
|
_, _ = fmt.Fprintf(h, "%d|%s|%s\n", m.ID, m.Role, m.Content)
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ContextManager) SetSemanticSummarizer(s SemanticSummarizer) {
|
||||||
|
if m == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
m.semanticSummarizer = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ContextManager) semanticSummarizerOrDefault() SemanticSummarizer {
|
||||||
|
if m == nil {
|
||||||
|
return &HeuristicSemanticSummarizer{}
|
||||||
|
}
|
||||||
|
m.mu.RLock()
|
||||||
|
s := m.semanticSummarizer
|
||||||
|
m.mu.RUnlock()
|
||||||
|
if s != nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return &HeuristicSemanticSummarizer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// applySemanticShadow evaluates shadow semantic summary without replacing deterministic fields.
|
||||||
|
// REPLACE is intentionally unwired: served narrative/facts stay deterministic.
|
||||||
|
func applySemanticShadow(ctx context.Context, sum *ChatSummary, msgs []ChatMessageRecord, summarizer SemanticSummarizer) {
|
||||||
|
if sum == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sum.Source = "deterministic"
|
||||||
|
if !SemanticSummaryShadowEnabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
minimized := MinimizeAuthorizedMessages(msgs)
|
||||||
|
digest := DigestAuthorizedInput(minimized)
|
||||||
|
report := &SemanticShadowReport{
|
||||||
|
Status: ShadowStatusFallback,
|
||||||
|
InputDigest: digest,
|
||||||
|
ToolPermissions: SemanticToolPermissions,
|
||||||
|
GeneratedAt: now,
|
||||||
|
}
|
||||||
|
if summarizer == nil {
|
||||||
|
report.FreshnessNote = fmt.Sprintf("semantic_shadow_fallback: summarizer_unavailable; deterministic retained at %s", now.UTC().Format(time.RFC3339))
|
||||||
|
sum.Shadow = report
|
||||||
|
sum.FreshnessNote = report.FreshnessNote
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, err := summarizer.Summarize(ctx, SemanticSummaryRequest{
|
||||||
|
SessionID: sum.SessionID,
|
||||||
|
Messages: minimized,
|
||||||
|
InputDigest: digest,
|
||||||
|
})
|
||||||
|
if err != nil || res == nil || strings.TrimSpace(res.Narrative) == "" {
|
||||||
|
reason := "empty_or_error"
|
||||||
|
if err != nil {
|
||||||
|
reason = "model_error"
|
||||||
|
}
|
||||||
|
report.FreshnessNote = fmt.Sprintf("semantic_shadow_fallback: %s; deterministic retained at %s", reason, now.UTC().Format(time.RFC3339))
|
||||||
|
sum.Shadow = report
|
||||||
|
sum.FreshnessNote = report.FreshnessNote
|
||||||
|
return
|
||||||
|
}
|
||||||
|
report.Status = ShadowStatusOK
|
||||||
|
report.Narrative = res.Narrative
|
||||||
|
report.Model = res.Model
|
||||||
|
report.Protocol = res.Protocol
|
||||||
|
if report.Protocol == "" {
|
||||||
|
report.Protocol = semanticProtocolV1
|
||||||
|
}
|
||||||
|
// Explicitly do NOT swap sum.Narrative even if REPLACE env is true.
|
||||||
|
_ = SemanticSummaryReplaceEnabled()
|
||||||
|
sum.Shadow = report
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyDigestSemanticShadow attaches shadow metadata to a daily digest without replacing rule fields.
|
||||||
|
func applyDigestSemanticShadow(ctx context.Context, digest *DailyDigest, msgs []ChatMessageRecord, summarizer SemanticSummarizer) {
|
||||||
|
if digest == nil || !SemanticSummaryShadowEnabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmp := &ChatSummary{SessionID: digest.SessionID, Narrative: digest.Headline}
|
||||||
|
applySemanticShadow(ctx, tmp, msgs, summarizer)
|
||||||
|
if tmp.Shadow == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
digest.Shadow = tmp.Shadow
|
||||||
|
digest.FreshnessNote = tmp.FreshnessNote
|
||||||
|
// Headline/KeyPoints remain rule/deterministic; shadow narrative is advisory only.
|
||||||
|
}
|
||||||
@@ -10,13 +10,16 @@ import (
|
|||||||
|
|
||||||
// ChatSummary 会话滚动摘要数据模型
|
// ChatSummary 会话滚动摘要数据模型
|
||||||
type ChatSummary struct {
|
type ChatSummary struct {
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
Narrative string `json:"narrative"` // 叙事摘要(≤300 字)
|
Narrative string `json:"narrative"` // 叙事摘要(≤300 字);PU6 shadow 下始终为 deterministic
|
||||||
Facts map[string]string `json:"facts"` // 硬事实槽位字典(永不被压缩稀释)
|
Facts map[string]string `json:"facts"` // 硬事实槽位字典(永不被压缩稀释)
|
||||||
OpenQuestions []string `json:"open_questions"` // 未决技术或商务待办
|
OpenQuestions []string `json:"open_questions"` // 未决技术或商务待办
|
||||||
CoveredCount int `json:"covered_count"` // 累计已覆盖并压缩的历史消息条数
|
CoveredCount int `json:"covered_count"` // 累计已覆盖并压缩的历史消息条数
|
||||||
CoveredUntil time.Time `json:"covered_until"` // 摘要覆盖消息的截止时间戳
|
CoveredUntil time.Time `json:"covered_until"` // 摘要覆盖消息的截止时间戳
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Source string `json:"source,omitempty"` // deterministic(served)
|
||||||
|
FreshnessNote string `json:"freshness_note,omitempty"` // shadow 失败时的可解释回退说明
|
||||||
|
Shadow *SemanticShadowReport `json:"shadow,omitempty"` // advisory only;REPLACE 未接线
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderPromptSnippet 渲染为 Prompt 中的【早期对话摘要】段
|
// RenderPromptSnippet 渲染为 Prompt 中的【早期对话摘要】段
|
||||||
@@ -188,6 +191,11 @@ func (m *ContextManager) GenerateRollingSummary(ctx context.Context, sessionID s
|
|||||||
fullNarrative = string(runes[len(runes)-260:]) + "..."
|
fullNarrative = string(runes[len(runes)-260:]) + "..."
|
||||||
}
|
}
|
||||||
newSum.Narrative = fullNarrative
|
newSum.Narrative = fullNarrative
|
||||||
|
newSum.Source = "deterministic"
|
||||||
|
|
||||||
|
// SEMANTIC_SUMMARY_SHADOW:仅在授权可见集合上评估语义摘要;失败保留 deterministic + freshness。
|
||||||
|
// SEMANTIC_SUMMARY_REPLACE 故意不接线,served Narrative 永不被语义结果替换。
|
||||||
|
applySemanticShadow(ctx, newSum, earlyMsgs, m.semanticSummarizerOrDefault())
|
||||||
|
|
||||||
// 保存并返回
|
// 保存并返回
|
||||||
_ = m.SaveChatSummary(ctx, newSum)
|
_ = m.SaveChatSummary(ctx, newSum)
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package ctxmgr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnvSemanticSummaryShadow = "SEMANTIC_SUMMARY_SHADOW"
|
||||||
|
EnvSemanticSummaryReplace = "SEMANTIC_SUMMARY_REPLACE"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SummaryFeatureFlags controls semantic summary rollout.
|
||||||
|
// REPLACE is reserved and must remain unwired in PU6 shadow delivery.
|
||||||
|
type SummaryFeatureFlags struct {
|
||||||
|
Shadow bool
|
||||||
|
Replace bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
summaryFlagsMu sync.RWMutex
|
||||||
|
activeSummaryFlags = loadSummaryFlagsFromEnv()
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadSummaryFlagsFromEnv() SummaryFeatureFlags {
|
||||||
|
return SummaryFeatureFlags{
|
||||||
|
Shadow: envBoolDefault(EnvSemanticSummaryShadow, false),
|
||||||
|
Replace: envBoolDefault(EnvSemanticSummaryReplace, false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func envBoolDefault(key string, def bool) bool {
|
||||||
|
v := strings.TrimSpace(os.Getenv(key))
|
||||||
|
if v == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return v == "1" || strings.EqualFold(v, "true") || strings.EqualFold(v, "yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentSummaryFlags returns the active summary feature flags.
|
||||||
|
func CurrentSummaryFlags() SummaryFeatureFlags {
|
||||||
|
summaryFlagsMu.RLock()
|
||||||
|
defer summaryFlagsMu.RUnlock()
|
||||||
|
return activeSummaryFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemanticSummaryShadowEnabled reports whether shadow semantic summary is on.
|
||||||
|
func SemanticSummaryShadowEnabled() bool {
|
||||||
|
return CurrentSummaryFlags().Shadow
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemanticSummaryReplaceEnabled reports the REPLACE env bit.
|
||||||
|
// PU6 must not use this to swap the served deterministic narrative.
|
||||||
|
func SemanticSummaryReplaceEnabled() bool {
|
||||||
|
return CurrentSummaryFlags().Replace
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSummaryFlagsForTest overrides flags in tests.
|
||||||
|
func SetSummaryFlagsForTest(f SummaryFeatureFlags) {
|
||||||
|
summaryFlagsMu.Lock()
|
||||||
|
activeSummaryFlags = f
|
||||||
|
summaryFlagsMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetSummaryFlagsForTest restores flags from the process environment.
|
||||||
|
func ResetSummaryFlagsForTest() {
|
||||||
|
summaryFlagsMu.Lock()
|
||||||
|
activeSummaryFlags = loadSummaryFlagsFromEnv()
|
||||||
|
summaryFlagsMu.Unlock()
|
||||||
|
}
|
||||||
+78
-77
@@ -17,39 +17,39 @@ const (
|
|||||||
|
|
||||||
// UserPreference 用户工作习惯与产出偏好画像
|
// UserPreference 用户工作习惯与产出偏好画像
|
||||||
type UserPreference struct {
|
type UserPreference struct {
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
AccountID string `json:"account_id"`
|
AccountID string `json:"account_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
CommunicationStyle CommunicationStyle `json:"communication_style"`
|
CommunicationStyle CommunicationStyle `json:"communication_style"`
|
||||||
FocusAreas []string `json:"focus_areas"` // 关注维度,如 "reg_compliance", "cost_budget", "timeline", "optical_quality"
|
FocusAreas []string `json:"focus_areas"` // 关注维度,如 "reg_compliance", "cost_budget", "timeline", "optical_quality"
|
||||||
OutputFormatPref string `json:"output_format_pref"` // 报告格式要求,如 "standard_report", "bilingual_brief", "ppt_bullets"
|
OutputFormatPref string `json:"output_format_pref"` // 报告格式要求,如 "standard_report", "bilingual_brief", "ppt_bullets"
|
||||||
CustomInstructions string `json:"custom_instructions"` // 用户直接指定的定制要求
|
CustomInstructions string `json:"custom_instructions"` // 用户直接指定的定制要求
|
||||||
MemoryFacts map[string]string `json:"memory_facts"` // 异步蒸馏出的长期事实记忆 (如 "预算通常按千万级申报", "偏好使用迈瑞作为对标竞品")
|
MemoryFacts map[string]string `json:"memory_facts"` // 异步蒸馏出的长期事实记忆 (如 "预算通常按千万级申报", "偏好使用迈瑞作为对标竞品")
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActivityItem 项目活动记录 (操作、决策、上传、下载)
|
// ActivityItem 项目活动记录 (操作、决策、上传、下载)
|
||||||
type ActivityItem struct {
|
type ActivityItem struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
ProjectID int64 `json:"project_id"`
|
ProjectID int64 `json:"project_id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
UserName string `json:"user_name"`
|
UserName string `json:"user_name"`
|
||||||
ActivityType string `json:"activity_type"` // decision, operation, upload, download, stage_advance, chat
|
ActivityType string `json:"activity_type"` // decision, operation, upload, download, stage_advance, chat
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Detail map[string]any `json:"detail,omitempty"`
|
Detail map[string]any `json:"detail,omitempty"`
|
||||||
RawWeight float64 `json:"raw_weight"` // 初始权重
|
RawWeight float64 `json:"raw_weight"` // 初始权重
|
||||||
DecayedWeight float64 `json:"decayed_weight"`// 时间衰减后当前有效权重
|
DecayedWeight float64 `json:"decayed_weight"` // 时间衰减后当前有效权重
|
||||||
IsMilestone bool `json:"is_milestone"` // 里程碑事件不衰减
|
IsMilestone bool `json:"is_milestone"` // 里程碑事件不衰减
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimeDecayConfig 时间权重衰减配置
|
// TimeDecayConfig 时间权重衰减配置
|
||||||
type TimeDecayConfig struct {
|
type TimeDecayConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
HalfLifeDays float64 `json:"half_life_days"` // 半衰期天数 (如 7.0 天)
|
HalfLifeDays float64 `json:"half_life_days"` // 半衰期天数 (如 7.0 天)
|
||||||
MinWeightFloor float64 `json:"min_weight_floor"` // 权重衰减下限 (如 0.10,防止被完全抹除)
|
MinWeightFloor float64 `json:"min_weight_floor"` // 权重衰减下限 (如 0.10,防止被完全抹除)
|
||||||
MilestoneExempt bool `json:"milestone_exempt"` // 里程碑事件是否豁免衰减 (默认 true)
|
MilestoneExempt bool `json:"milestone_exempt"` // 里程碑事件是否豁免衰减 (默认 true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultTimeDecayConfig 默认时间衰减配置
|
// DefaultTimeDecayConfig 默认时间衰减配置
|
||||||
@@ -64,40 +64,40 @@ func DefaultTimeDecayConfig() TimeDecayConfig {
|
|||||||
|
|
||||||
// SingleUserContext 单用户私有上下文
|
// SingleUserContext 单用户私有上下文
|
||||||
type SingleUserContext struct {
|
type SingleUserContext struct {
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
AccountID string `json:"account_id"`
|
AccountID string `json:"account_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Preference *UserPreference `json:"preference"`
|
Preference *UserPreference `json:"preference"`
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
RecentChats []ChatMessageRecord `json:"recent_chats"`
|
RecentChats []ChatMessageRecord `json:"recent_chats"`
|
||||||
MyActivities []ActivityItem `json:"my_activities"`
|
MyActivities []ActivityItem `json:"my_activities"`
|
||||||
MyFiles []*storage.FileReference `json:"my_files"`
|
MyFiles []*storage.FileReference `json:"my_files"`
|
||||||
AssembledAt time.Time `json:"assembled_at"`
|
AssembledAt time.Time `json:"assembled_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProjectCollectiveContext 项目级多方协同上下文
|
// ProjectCollectiveContext 项目级多方协同上下文
|
||||||
type ProjectCollectiveContext struct {
|
type ProjectCollectiveContext struct {
|
||||||
ProjectID int64 `json:"project_id"`
|
ProjectID int64 `json:"project_id"`
|
||||||
ProjectCode string `json:"project_code"`
|
ProjectCode string `json:"project_code"`
|
||||||
ProjectName string `json:"project_name"`
|
ProjectName string `json:"project_name"`
|
||||||
CurrentStage string `json:"current_stage"`
|
CurrentStage string `json:"current_stage"`
|
||||||
CreatorUserID int64 `json:"creator_user_id,omitempty"`
|
CreatorUserID int64 `json:"creator_user_id,omitempty"`
|
||||||
ActiveWorkflowKey string `json:"active_workflow_key,omitempty"`
|
ActiveWorkflowKey string `json:"active_workflow_key,omitempty"`
|
||||||
WorkflowSelectionVersion int `json:"workflow_selection_version,omitempty"`
|
WorkflowSelectionVersion int `json:"workflow_selection_version,omitempty"`
|
||||||
BlackboardFacts map[string]string `json:"blackboard_facts"`
|
BlackboardFacts map[string]string `json:"blackboard_facts"`
|
||||||
AllActivities []ActivityItem `json:"all_activities"`
|
AllActivities []ActivityItem `json:"all_activities"`
|
||||||
MilestoneDecisions []ActivityItem `json:"milestone_decisions"`
|
MilestoneDecisions []ActivityItem `json:"milestone_decisions"`
|
||||||
SharedFiles []*storage.FileReference `json:"shared_files"`
|
SharedFiles []*storage.FileReference `json:"shared_files"`
|
||||||
ParticipatingUsers []UserSummary `json:"participating_users"`
|
ParticipatingUsers []UserSummary `json:"participating_users"`
|
||||||
AssembledAt time.Time `json:"assembled_at"`
|
AssembledAt time.Time `json:"assembled_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserSummary 参与项目的用户摘要
|
// UserSummary 参与项目的用户摘要
|
||||||
type UserSummary struct {
|
type UserSummary struct {
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatMessageRecord 结构化会话消息
|
// ChatMessageRecord 结构化会话消息
|
||||||
@@ -181,16 +181,16 @@ type ChatCategoryStat struct {
|
|||||||
|
|
||||||
// BlendedSquadContext 智能体小队混合上下文 (用于注入到 Agent Prompt 中)
|
// BlendedSquadContext 智能体小队混合上下文 (用于注入到 Agent Prompt 中)
|
||||||
type BlendedSquadContext struct {
|
type BlendedSquadContext struct {
|
||||||
ProjectName string `json:"project_name"`
|
ProjectName string `json:"project_name"`
|
||||||
CurrentStage string `json:"current_stage"`
|
CurrentStage string `json:"current_stage"`
|
||||||
TaskKey string `json:"task_key"`
|
TaskKey string `json:"task_key"`
|
||||||
AgentRole string `json:"agent_role"`
|
AgentRole string `json:"agent_role"`
|
||||||
BlackboardFacts map[string]string `json:"blackboard_facts"`
|
BlackboardFacts map[string]string `json:"blackboard_facts"`
|
||||||
KeyDecisions []string `json:"key_decisions"`
|
KeyDecisions []string `json:"key_decisions"`
|
||||||
RelevantFiles []string `json:"relevant_files"`
|
RelevantFiles []string `json:"relevant_files"`
|
||||||
TriggerUserPreference *UserPreference `json:"trigger_user_preference,omitempty"`
|
TriggerUserPreference *UserPreference `json:"trigger_user_preference,omitempty"`
|
||||||
PersonalizedGuidance string `json:"personalized_guidance"` // 针对当前用户偏好生成的注入指导语
|
PersonalizedGuidance string `json:"personalized_guidance"` // 针对当前用户偏好生成的注入指导语
|
||||||
AssembledAt time.Time `json:"assembled_at"`
|
AssembledAt time.Time `json:"assembled_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionFilter 历史会话查询过滤条件
|
// SessionFilter 历史会话查询过滤条件
|
||||||
@@ -308,25 +308,26 @@ type DigestDeliverable struct {
|
|||||||
|
|
||||||
// DailyDigest 按日智能摘要
|
// DailyDigest 按日智能摘要
|
||||||
type DailyDigest struct {
|
type DailyDigest struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
ProjectID int64 `json:"project_id,omitempty"`
|
ProjectID int64 `json:"project_id,omitempty"`
|
||||||
DayKey string `json:"day_key"`
|
DayKey string `json:"day_key"`
|
||||||
Headline string `json:"headline"`
|
Headline string `json:"headline"`
|
||||||
KeyPoints []string `json:"key_points"`
|
KeyPoints []string `json:"key_points"`
|
||||||
Deliverables []DigestDeliverable `json:"deliverables"`
|
Deliverables []DigestDeliverable `json:"deliverables"`
|
||||||
Decisions []string `json:"decisions"`
|
Decisions []string `json:"decisions"`
|
||||||
OpenQuestions []string `json:"open_questions"`
|
OpenQuestions []string `json:"open_questions"`
|
||||||
StageProgress string `json:"stage_progress"`
|
StageProgress string `json:"stage_progress"`
|
||||||
FirstMessageID int64 `json:"first_message_id"`
|
FirstMessageID int64 `json:"first_message_id"`
|
||||||
LastMessageID int64 `json:"last_message_id"`
|
LastMessageID int64 `json:"last_message_id"`
|
||||||
MessageCount int `json:"message_count"`
|
MessageCount int `json:"message_count"`
|
||||||
Status string `json:"status"` // pending | generating | ready | fallback | failed
|
Status string `json:"status"` // pending | generating | ready | fallback | failed
|
||||||
Source string `json:"source"` // llm | rule
|
Source string `json:"source"` // llm | rule
|
||||||
Model string `json:"model,omitempty"`
|
Model string `json:"model,omitempty"`
|
||||||
TokenUsage int `json:"token_usage,omitempty"`
|
TokenUsage int `json:"token_usage,omitempty"`
|
||||||
DigestVersion int `json:"digest_version"`
|
DigestVersion int `json:"digest_version"`
|
||||||
GeneratedAt *time.Time `json:"generated_at,omitempty"`
|
GeneratedAt *time.Time `json:"generated_at,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
FreshnessNote string `json:"freshness_note,omitempty"`
|
||||||
|
Shadow *SemanticShadowReport `json:"shadow,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user