feat: Round 5 - Memory Service, Tool Engine, Call Records, Thinking Logs

- Fix: Session history flash (race condition + WS guard)
- Fix: Chat background overlay + sidebar transparency
- Fix: IoT device control (Chinese action names, status field)
- Feat: Independent memory-service (port 8091, 13 endpoints)
- Feat: Independent tool-engine service (port 8092, 13 tools)
- Feat: Tool call logs with paginated DevTools panel
- Feat: Thinking log records with DevTools panel
- Feat: Future development roadmap document
- Chore: Updated .gitignore, go.work, DevTools config
- Chore: 5-service health check, project review docs
This commit is contained in:
2026-05-18 20:05:14 +08:00
parent b6ec36886c
commit 78e3f450c2
54 changed files with 7846 additions and 106 deletions
+36 -6
View File
@@ -50,6 +50,9 @@ type Thinker struct {
adminUserID string // 管理员用户 ID
adminSessionID string // 管理员主对话 session ID
// 记忆服务 HTTP 客户端(用于持久化思考日志)
memClient *memory.Client
pendingThoughts []*PendingThought
lastUserMessage time.Time
@@ -90,6 +93,7 @@ func NewThinker(
convStore *ctxbuild.ConversationStore,
adminUserID string,
adminSessionID string,
memClient *memory.Client,
) *Thinker {
return &Thinker{
enabled: cfg.Enabled,
@@ -106,6 +110,7 @@ func NewThinker(
convStore: convStore,
adminUserID: adminUserID,
adminSessionID: adminSessionID,
memClient: memClient,
pendingThoughts: make([]*PendingThought, 0),
lastUserMessage: time.Now(),
stopCh: make(chan struct{}),
@@ -281,6 +286,7 @@ func (t *Thinker) performThink() {
maxToolRounds := 3
var finalContent string
var totalToolCalls int
var toolCallRecords []map[string]interface{}
for round := 0; round <= maxToolRounds; round++ {
resp, err := t.llmAdapter.ChatWithTools(ctx, messages, openAITools)
@@ -327,6 +333,10 @@ func (t *Thinker) performThink() {
})
totalToolCalls++
toolCallRecords = append(toolCallRecords, map[string]interface{}{
"name": tc.Name,
"args": args,
})
}
// 最后一轮:即使有 tool_calls 也强制停止
@@ -348,8 +358,16 @@ func (t *Thinker) performThink() {
return
}
// 8. 存储思考结果
t.storeThought(finalContent)
// 序列化工具调用记录
toolCallsJSON := "[]"
if len(toolCallRecords) > 0 {
if data, err := json.Marshal(toolCallRecords); err == nil {
toolCallsJSON = string(data)
}
}
// 8. 存储思考结果(内存队列 + 持久化到 memory-service
t.storeThought(finalContent, toolCallsJSON, totalToolCalls)
log.Printf("[后台思考] 完成 (内容长度=%d, 工具调用=%d次)", len(finalContent), totalToolCalls)
@@ -475,11 +493,9 @@ func (t *Thinker) buildOpenAITools() []llm.OpenAITool {
return result
}
// storeThought 存储思考结果到待推送队列
func (t *Thinker) storeThought(content string) {
// storeThought 存储思考结果到待推送队列,并异步持久化到 memory-service
func (t *Thinker) storeThought(content string, toolCallsJSON string, toolCallCount int) {
t.mu.Lock()
defer t.mu.Unlock()
t.pendingThoughts = append(t.pendingThoughts, &PendingThought{
Content: content,
CreatedAt: time.Now(),
@@ -490,8 +506,22 @@ func (t *Thinker) storeThought(content string) {
if len(t.pendingThoughts) > 10 {
t.pendingThoughts = t.pendingThoughts[len(t.pendingThoughts)-10:]
}
t.mu.Unlock()
log.Printf("[后台思考] 思考已存储 (当前累积 %d 条待推送思考)", len(t.pendingThoughts))
// 异步持久化到 memory-service (不阻塞思考循环)
if t.memClient != nil {
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := t.memClient.SaveThinkingLog(ctx, t.adminUserID, content, toolCallsJSON, toolCallCount, len(content)); err != nil {
log.Printf("[后台思考] 持久化思考日志失败: %v", err)
} else {
log.Printf("[后台思考] 思考日志已持久化 (长度=%d, 工具调用=%d)", len(content), toolCallCount)
}
}()
}
}
// extractMemoriesFromThinking 从思考结果中提取记忆(异步执行)