feat: Phase 2 — chat templates, reasoning parser, collapsible TUI, mouse support

- internal/llama/template.go: ChatML/Llama3 template engine with stop tokens
- internal/agent/reasoning.go: parse <think>/<reasoning> tags from LLM output
- companion/promptbuilder.go: output structured BuildResult, add date/time context
- agent/agent.go: wire templates + reasoning split into message loop
- storage: add reasoning TEXT column to messages table + migration
- channel/tui.go: collapsible reasoning (tab toggle), mouse scroll, polished dimmed style
- config: CHAT_TEMPLATE env var (chatml|llama3|none)
- plan.md: mark Phase 2 complete, add themes TODO
This commit is contained in:
2026-06-10 09:42:45 +03:30
parent f57b42fd6e
commit 3895ca68c0
14 changed files with 327 additions and 144 deletions

View File

@@ -72,7 +72,7 @@ func (a *Agent) HandleMessage(ctx context.Context, content string) (string, erro
slog.Warn("memory search failed", "error", err)
}
prompt, err := a.companion.BuildPrompt(ctx, &companion.PromptInput{
buildResult, err := a.companion.BuildPrompt(ctx, &companion.PromptInput{
UserMessage: content,
RecentMessages: a.messages,
Memories: memories,
@@ -81,11 +81,30 @@ func (a *Agent) HandleMessage(ctx context.Context, content string) (string, erro
return "", fmt.Errorf("build prompt: %w", err)
}
segments := []types.PromptSegment{
{Type: types.SegSystem, Content: buildResult.SystemContent},
}
start := 0
if len(a.messages) > 10 {
start = len(a.messages) - 10
}
for _, msg := range a.messages[start:] {
segType := types.SegUser
if msg.Role == types.RoleAssistant {
segType = types.SegAssistant
}
segments = append(segments, types.PromptSegment{Type: segType, Content: msg.Content})
}
tmpl := llama.NewTemplateEngine(a.cfg.ChatTemplate)
prompt := tmpl.Build(segments)
resp, err := a.llm.Complete(&llama.CompletionRequest{
Prompt: prompt,
Temperature: a.cfg.Temperature,
TopP: a.cfg.TopP,
MaxTokens: a.cfg.MaxTokens,
Stop: tmpl.StopTokens(),
Stream: false,
CachePrompt: true,
})
@@ -93,10 +112,13 @@ func (a *Agent) HandleMessage(ctx context.Context, content string) (string, erro
return "", fmt.Errorf("llm complete: %w", err)
}
parsed := ParseResponse(resp.Content)
assistantMsg := types.Message{
ID: fmt.Sprintf("%x", time.Now().UnixNano()),
Role: types.RoleAssistant,
Content: resp.Content,
Content: parsed.Content,
Reasoning: parsed.Reasoning,
CreatedAt: time.Now(),
}
a.messages = append(a.messages, assistantMsg)
@@ -108,7 +130,7 @@ func (a *Agent) HandleMessage(ctx context.Context, content string) (string, erro
}
}()
return resp.Content, nil
return parsed.Content, nil
}
func (a *Agent) Messages() []types.Message {
@@ -130,8 +152,8 @@ func (a *Agent) saveMessage(msg types.Message) {
toolCallJSON = string(b)
}
err := a.store.Exec(
"INSERT INTO messages (id, conversation_id, role, content, tool_call, created_at) VALUES (?, ?, ?, ?, ?, ?)",
msg.ID, a.conversationID, string(msg.Role), msg.Content, toolCallJSON, msg.CreatedAt.Format(time.RFC3339),
"INSERT INTO messages (id, conversation_id, role, content, reasoning, tool_call, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
msg.ID, a.conversationID, string(msg.Role), msg.Content, msg.Reasoning, toolCallJSON, msg.CreatedAt.Format(time.RFC3339),
)
if err != nil {
slog.Warn("failed to save message", "error", err)