fix(seahorse): persist reasoning_content in sqlite history
This commit is contained in:
parent
db1bc6a1f8
commit
ee3c815aff
6 changed files with 385 additions and 25 deletions
|
|
@ -46,6 +46,7 @@ func runSchema(db *sql.DB) error {
|
|||
conversation_id INTEGER NOT NULL REFERENCES conversations(conversation_id),
|
||||
role TEXT NOT NULL,
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
reasoning_content TEXT NOT NULL DEFAULT '',
|
||||
token_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)`,
|
||||
|
|
@ -157,9 +158,57 @@ func runSchema(db *sql.DB) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ensureMessagesReasoningContentColumn(db); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureMessagesReasoningContentColumn(db *sql.DB) error {
|
||||
hasColumn, err := tableHasColumn(db, "messages", "reasoning_content")
|
||||
if err != nil {
|
||||
return fmt.Errorf("check messages.reasoning_content: %w", err)
|
||||
}
|
||||
if hasColumn {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := db.Exec(`ALTER TABLE messages ADD COLUMN reasoning_content TEXT NOT NULL DEFAULT ''`); err != nil {
|
||||
return fmt.Errorf("add messages.reasoning_content: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tableHasColumn(db *sql.DB, tableName, columnName string) (bool, error) {
|
||||
rows, err := db.Query(fmt.Sprintf(`PRAGMA table_info(%s)`, tableName))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
cid int
|
||||
name string
|
||||
columnType string
|
||||
notNull int
|
||||
defaultVal sql.NullString
|
||||
pk int
|
||||
)
|
||||
if err := rows.Scan(&cid, &name, &columnType, ¬Null, &defaultVal, &pk); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if name == columnName {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// checkFTS5Support verifies that SQLite has FTS5 with trigram tokenizer enabled.
|
||||
// This is required for full-text search with CJK (Chinese, Japanese, Korean) support.
|
||||
func checkFTS5Support(db *sql.DB) error {
|
||||
|
|
|
|||
|
|
@ -91,6 +91,51 @@ func TestRunMigrationsIdempotent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRunSchemaAddsMessagesReasoningContentColumn(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
|
||||
_, err := db.Exec(`CREATE TABLE messages (
|
||||
message_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conversation_id INTEGER NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
token_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)`)
|
||||
if err != nil {
|
||||
t.Fatalf("create legacy messages table: %v", err)
|
||||
}
|
||||
|
||||
if err := runSchema(db); err != nil {
|
||||
t.Fatalf("runSchema: %v", err)
|
||||
}
|
||||
|
||||
var count int
|
||||
err = db.QueryRow(`SELECT count(*) FROM pragma_table_info('messages') WHERE name = 'reasoning_content'`).Scan(&count)
|
||||
if err != nil {
|
||||
t.Fatalf("query pragma_table_info: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("reasoning_content column count = %d, want 1", count)
|
||||
}
|
||||
|
||||
_, err = db.Exec(
|
||||
`INSERT INTO conversations (session_key, created_at, updated_at) VALUES (?, datetime('now'), datetime('now'))`,
|
||||
"reasoning-column-test",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("insert conversation: %v", err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(
|
||||
`INSERT INTO messages (conversation_id, role, content, reasoning_content, token_count)
|
||||
VALUES (1, 'assistant', 'answer', 'thinking', 1)`,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("insert message with reasoning_content: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrationConversationUnique(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
if err := runSchema(db); err != nil {
|
||||
|
|
|
|||
|
|
@ -253,9 +253,23 @@ func (e *Engine) Ingest(ctx context.Context, sessionKey string, messages []Messa
|
|||
var added *Message
|
||||
var err error
|
||||
if len(msg.Parts) > 0 {
|
||||
added, err = e.store.AddMessageWithParts(ctx, conv.ConversationID, msg.Role, msg.Parts, msg.TokenCount)
|
||||
added, err = e.store.AddMessageWithPartsAndReasoning(
|
||||
ctx,
|
||||
conv.ConversationID,
|
||||
msg.Role,
|
||||
msg.Parts,
|
||||
msg.ReasoningContent,
|
||||
msg.TokenCount,
|
||||
)
|
||||
} else {
|
||||
added, err = e.store.AddMessage(ctx, conv.ConversationID, msg.Role, msg.Content, msg.TokenCount)
|
||||
added, err = e.store.AddMessageWithReasoning(
|
||||
ctx,
|
||||
conv.ConversationID,
|
||||
msg.Role,
|
||||
msg.Content,
|
||||
msg.ReasoningContent,
|
||||
msg.TokenCount,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add message: %w", err)
|
||||
|
|
@ -532,13 +546,13 @@ func truncate(s string, maxLen int) string {
|
|||
return s[:maxLen] + "..."
|
||||
}
|
||||
|
||||
// messageMatches compares two messages using (role, content) or (role, parts).
|
||||
// TokenCount is NOT compared because it may be re-estimated differently
|
||||
// during bootstrap (e.g., via tokenizer.EstimateMessageTokens).
|
||||
// messageMatches compares two messages using role + reasoning_content and then
|
||||
// either content or parts. TokenCount is NOT compared because it may be
|
||||
// re-estimated differently during bootstrap (e.g., via tokenizer.EstimateMessageTokens).
|
||||
// For messages with Parts (tool_use, tool_result), compare Parts instead of Content
|
||||
// since AddMessageWithParts stores empty Content in DB.
|
||||
// because structured messages are matched by their parts payload.
|
||||
func messageMatches(a, b Message) bool {
|
||||
if a.Role != b.Role {
|
||||
if a.Role != b.Role || a.ReasoningContent != b.ReasoningContent {
|
||||
return false
|
||||
}
|
||||
// If either message has Parts, compare Parts
|
||||
|
|
|
|||
|
|
@ -320,6 +320,108 @@ func TestEngineIngestWithParts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEngineIngestPreservesReasoningContent(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
ctx := context.Background()
|
||||
|
||||
msgs := []Message{
|
||||
{
|
||||
Role: "assistant",
|
||||
Content: "world",
|
||||
ReasoningContent: "let me think this through",
|
||||
TokenCount: 4,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := eng.Ingest(ctx, "agent:reasoning", msgs)
|
||||
if err != nil {
|
||||
t.Fatalf("Ingest: %v", err)
|
||||
}
|
||||
|
||||
conv, _ := eng.store.GetOrCreateConversation(ctx, "agent:reasoning")
|
||||
stored, err := eng.store.GetMessages(ctx, conv.ConversationID, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMessages: %v", err)
|
||||
}
|
||||
if len(stored) != 1 {
|
||||
t.Fatalf("stored messages = %d, want 1", len(stored))
|
||||
}
|
||||
if stored[0].ReasoningContent != "let me think this through" {
|
||||
t.Errorf(
|
||||
"stored[0].ReasoningContent = %q, want %q",
|
||||
stored[0].ReasoningContent,
|
||||
"let me think this through",
|
||||
)
|
||||
}
|
||||
|
||||
result, err := eng.Assemble(ctx, "agent:reasoning", AssembleInput{Budget: 1000})
|
||||
if err != nil {
|
||||
t.Fatalf("Assemble: %v", err)
|
||||
}
|
||||
if len(result.Messages) != 1 {
|
||||
t.Fatalf("assembled messages = %d, want 1", len(result.Messages))
|
||||
}
|
||||
if result.Messages[0].ReasoningContent != "let me think this through" {
|
||||
t.Errorf(
|
||||
"assembled reasoning = %q, want %q",
|
||||
result.Messages[0].ReasoningContent,
|
||||
"let me think this through",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineIngestWithPartsPreservesReasoningContent(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
ctx := context.Background()
|
||||
|
||||
msgs := []Message{
|
||||
{
|
||||
Role: "assistant",
|
||||
ReasoningContent: "I need to inspect the file first",
|
||||
TokenCount: 10,
|
||||
Parts: []MessagePart{
|
||||
{Type: "tool_use", Name: "read_file", Arguments: `{"path":"/tmp/test"}`, ToolCallID: "tc_123"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := eng.Ingest(ctx, "agent:parts-reasoning", msgs)
|
||||
if err != nil {
|
||||
t.Fatalf("Ingest: %v", err)
|
||||
}
|
||||
|
||||
conv, _ := eng.store.GetOrCreateConversation(ctx, "agent:parts-reasoning")
|
||||
stored, err := eng.store.GetMessages(ctx, conv.ConversationID, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMessages: %v", err)
|
||||
}
|
||||
if len(stored) != 1 {
|
||||
t.Fatalf("stored messages = %d, want 1", len(stored))
|
||||
}
|
||||
if stored[0].ReasoningContent != "I need to inspect the file first" {
|
||||
t.Errorf(
|
||||
"stored reasoning = %q, want %q",
|
||||
stored[0].ReasoningContent,
|
||||
"I need to inspect the file first",
|
||||
)
|
||||
}
|
||||
|
||||
result, err := eng.Assemble(ctx, "agent:parts-reasoning", AssembleInput{Budget: 1000})
|
||||
if err != nil {
|
||||
t.Fatalf("Assemble: %v", err)
|
||||
}
|
||||
if len(result.Messages) != 1 {
|
||||
t.Fatalf("assembled messages = %d, want 1", len(result.Messages))
|
||||
}
|
||||
if result.Messages[0].ReasoningContent != "I need to inspect the file first" {
|
||||
t.Errorf(
|
||||
"assembled reasoning = %q, want %q",
|
||||
result.Messages[0].ReasoningContent,
|
||||
"I need to inspect the file first",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineIngestAssemblePreservesParts(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
ctx := context.Background()
|
||||
|
|
@ -514,6 +616,52 @@ func TestEngineBootstrapIdempotent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBootstrapRepairsMissingReasoningContent(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
ctx := context.Background()
|
||||
sessionKey := "agent:repair-reasoning"
|
||||
|
||||
conv, err := eng.store.GetOrCreateConversation(ctx, sessionKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateConversation: %v", err)
|
||||
}
|
||||
|
||||
userMsg, err := eng.store.AddMessage(ctx, conv.ConversationID, "user", "hello", 3)
|
||||
if err != nil {
|
||||
t.Fatalf("AddMessage user: %v", err)
|
||||
}
|
||||
assistantMsg, err := eng.store.AddMessage(ctx, conv.ConversationID, "assistant", "world", 3)
|
||||
if err != nil {
|
||||
t.Fatalf("AddMessage assistant: %v", err)
|
||||
}
|
||||
if err := eng.store.AppendContextMessages(ctx, conv.ConversationID, []int64{userMsg.ID, assistantMsg.ID}); err != nil {
|
||||
t.Fatalf("AppendContextMessages: %v", err)
|
||||
}
|
||||
|
||||
err = eng.Bootstrap(ctx, sessionKey, []Message{
|
||||
{Role: "user", Content: "hello", TokenCount: 3},
|
||||
{Role: "assistant", Content: "world", ReasoningContent: "let me think this through", TokenCount: 3},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
}
|
||||
|
||||
stored, err := eng.store.GetMessages(ctx, conv.ConversationID, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMessages: %v", err)
|
||||
}
|
||||
if len(stored) != 2 {
|
||||
t.Fatalf("stored messages = %d, want 2", len(stored))
|
||||
}
|
||||
if stored[1].ReasoningContent != "let me think this through" {
|
||||
t.Errorf(
|
||||
"stored[1].ReasoningContent = %q, want %q",
|
||||
stored[1].ReasoningContent,
|
||||
"let me think this through",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineBootstrapDelta(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
|
|
@ -162,20 +162,31 @@ func (s *Store) getMessageTimeRange(ctx context.Context, convID int64) (time.Tim
|
|||
|
||||
// AddMessage appends a message to a conversation.
|
||||
func (s *Store) AddMessage(ctx context.Context, convID int64, role, content string, tokenCount int) (*Message, error) {
|
||||
return s.AddMessageWithReasoning(ctx, convID, role, content, "", tokenCount)
|
||||
}
|
||||
|
||||
// AddMessageWithReasoning appends a message with reasoning content to a conversation.
|
||||
func (s *Store) AddMessageWithReasoning(
|
||||
ctx context.Context,
|
||||
convID int64,
|
||||
role, content, reasoningContent string,
|
||||
tokenCount int,
|
||||
) (*Message, error) {
|
||||
result, err := s.db.ExecContext(ctx,
|
||||
"INSERT INTO messages (conversation_id, role, content, token_count) VALUES (?, ?, ?, ?)",
|
||||
convID, role, content, tokenCount,
|
||||
"INSERT INTO messages (conversation_id, role, content, reasoning_content, token_count) VALUES (?, ?, ?, ?, ?)",
|
||||
convID, role, content, reasoningContent, tokenCount,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add message: %w", err)
|
||||
}
|
||||
id, _ := result.LastInsertId()
|
||||
return &Message{
|
||||
ID: id,
|
||||
ConversationID: convID,
|
||||
Role: role,
|
||||
Content: content,
|
||||
TokenCount: tokenCount,
|
||||
ID: id,
|
||||
ConversationID: convID,
|
||||
Role: role,
|
||||
Content: content,
|
||||
ReasoningContent: reasoningContent,
|
||||
TokenCount: tokenCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -212,6 +223,18 @@ func (s *Store) AddMessageWithParts(
|
|||
role string,
|
||||
parts []MessagePart,
|
||||
tokenCount int,
|
||||
) (*Message, error) {
|
||||
return s.AddMessageWithPartsAndReasoning(ctx, convID, role, parts, "", tokenCount)
|
||||
}
|
||||
|
||||
// AddMessageWithPartsAndReasoning adds a message with structured parts and reasoning content.
|
||||
func (s *Store) AddMessageWithPartsAndReasoning(
|
||||
ctx context.Context,
|
||||
convID int64,
|
||||
role string,
|
||||
parts []MessagePart,
|
||||
reasoningContent string,
|
||||
tokenCount int,
|
||||
) (*Message, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
|
|
@ -223,8 +246,8 @@ func (s *Store) AddMessageWithParts(
|
|||
readableContent := partsToReadableContent(parts)
|
||||
|
||||
result, err := tx.ExecContext(ctx,
|
||||
"INSERT INTO messages (conversation_id, role, content, token_count) VALUES (?, ?, ?, ?)",
|
||||
convID, role, readableContent, tokenCount,
|
||||
"INSERT INTO messages (conversation_id, role, content, reasoning_content, token_count) VALUES (?, ?, ?, ?, ?)",
|
||||
convID, role, readableContent, reasoningContent, tokenCount,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add message: %w", err)
|
||||
|
|
@ -256,11 +279,12 @@ func (s *Store) AddMessageWithParts(
|
|||
|
||||
// Return message with parts
|
||||
msg := &Message{
|
||||
ID: msgID,
|
||||
ConversationID: convID,
|
||||
Role: role,
|
||||
TokenCount: tokenCount,
|
||||
Parts: make([]MessagePart, len(parts)),
|
||||
ID: msgID,
|
||||
ConversationID: convID,
|
||||
Role: role,
|
||||
ReasoningContent: reasoningContent,
|
||||
TokenCount: tokenCount,
|
||||
Parts: make([]MessagePart, len(parts)),
|
||||
}
|
||||
for i, p := range parts {
|
||||
p.MessageID = msgID
|
||||
|
|
@ -271,7 +295,7 @@ func (s *Store) AddMessageWithParts(
|
|||
|
||||
// GetMessages retrieves messages for a conversation.
|
||||
func (s *Store) GetMessages(ctx context.Context, convID int64, limit int, beforeID int64) ([]Message, error) {
|
||||
query := "SELECT message_id, conversation_id, role, content, token_count, created_at FROM messages WHERE conversation_id = ?"
|
||||
query := "SELECT message_id, conversation_id, role, content, reasoning_content, token_count, created_at FROM messages WHERE conversation_id = ?"
|
||||
args := []any{convID}
|
||||
if beforeID > 0 {
|
||||
query += " AND message_id < ?"
|
||||
|
|
@ -298,6 +322,7 @@ func (s *Store) GetMessages(ctx context.Context, convID int64, limit int, before
|
|||
&msg.ConversationID,
|
||||
&msg.Role,
|
||||
&msg.Content,
|
||||
&msg.ReasoningContent,
|
||||
&msg.TokenCount,
|
||||
&createdAt,
|
||||
); err != nil {
|
||||
|
|
@ -336,9 +361,9 @@ func (s *Store) GetMessageByID(ctx context.Context, messageID int64) (*Message,
|
|||
var msg Message
|
||||
var createdAt string
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
"SELECT message_id, conversation_id, role, content, token_count, created_at FROM messages WHERE message_id = ?",
|
||||
"SELECT message_id, conversation_id, role, content, reasoning_content, token_count, created_at FROM messages WHERE message_id = ?",
|
||||
messageID,
|
||||
).Scan(&msg.ID, &msg.ConversationID, &msg.Role, &msg.Content, &msg.TokenCount, &createdAt)
|
||||
).Scan(&msg.ID, &msg.ConversationID, &msg.Role, &msg.Content, &msg.ReasoningContent, &msg.TokenCount, &createdAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("message %d not found", messageID)
|
||||
}
|
||||
|
|
@ -534,7 +559,7 @@ func (s *Store) LinkSummaryToMessages(ctx context.Context, summaryID string, mes
|
|||
// GetSummarySourceMessages retrieves source messages for a summary.
|
||||
func (s *Store) GetSummarySourceMessages(ctx context.Context, summaryID string) ([]Message, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT m.message_id, m.conversation_id, m.role, m.content, m.token_count, m.created_at
|
||||
`SELECT m.message_id, m.conversation_id, m.role, m.content, m.reasoning_content, m.token_count, m.created_at
|
||||
FROM summary_messages sm
|
||||
JOIN messages m ON m.message_id = sm.message_id
|
||||
WHERE sm.summary_id = ?
|
||||
|
|
@ -555,6 +580,7 @@ func (s *Store) GetSummarySourceMessages(ctx context.Context, summaryID string)
|
|||
&msg.ConversationID,
|
||||
&msg.Role,
|
||||
&msg.Content,
|
||||
&msg.ReasoningContent,
|
||||
&msg.TokenCount,
|
||||
&createdAt,
|
||||
); err != nil {
|
||||
|
|
|
|||
|
|
@ -199,6 +199,47 @@ func TestStoreAddAndGetMessages(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStoreAddAndGetMessagesWithReasoningContent(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
conv, _ := s.GetOrCreateConversation(ctx, "agent:reasoning")
|
||||
|
||||
msg, err := s.AddMessageWithReasoning(
|
||||
ctx,
|
||||
conv.ConversationID,
|
||||
"assistant",
|
||||
"hello world",
|
||||
"let me think",
|
||||
5,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("AddMessageWithReasoning: %v", err)
|
||||
}
|
||||
if msg.ReasoningContent != "let me think" {
|
||||
t.Fatalf("ReasoningContent = %q, want %q", msg.ReasoningContent, "let me think")
|
||||
}
|
||||
|
||||
msgs, err := s.GetMessages(ctx, conv.ConversationID, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMessages: %v", err)
|
||||
}
|
||||
if len(msgs) != 1 {
|
||||
t.Fatalf("got %d messages, want 1", len(msgs))
|
||||
}
|
||||
if msgs[0].ReasoningContent != "let me think" {
|
||||
t.Errorf("ReasoningContent = %q, want %q", msgs[0].ReasoningContent, "let me think")
|
||||
}
|
||||
|
||||
found, err := s.GetMessageByID(ctx, msg.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMessageByID: %v", err)
|
||||
}
|
||||
if found.ReasoningContent != "let me think" {
|
||||
t.Errorf("GetMessageByID ReasoningContent = %q, want %q", found.ReasoningContent, "let me think")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreAddMessageWithParts(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
|
@ -233,6 +274,43 @@ func TestStoreAddMessageWithParts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStoreAddMessageWithPartsAndReasoningContent(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
conv, _ := s.GetOrCreateConversation(ctx, "agent:parts-reasoning")
|
||||
|
||||
parts := []MessagePart{
|
||||
{Type: "tool_use", Name: "read_file", Arguments: `{"path":"/tmp/test"}`, ToolCallID: "tc_123"},
|
||||
}
|
||||
_, err := s.AddMessageWithPartsAndReasoning(
|
||||
ctx,
|
||||
conv.ConversationID,
|
||||
"assistant",
|
||||
parts,
|
||||
"need to inspect the file first",
|
||||
10,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("AddMessageWithPartsAndReasoning: %v", err)
|
||||
}
|
||||
|
||||
msgs, err := s.GetMessages(ctx, conv.ConversationID, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMessages: %v", err)
|
||||
}
|
||||
if len(msgs) != 1 {
|
||||
t.Fatalf("expected 1 message, got %d", len(msgs))
|
||||
}
|
||||
if msgs[0].ReasoningContent != "need to inspect the file first" {
|
||||
t.Errorf(
|
||||
"ReasoningContent = %q, want %q",
|
||||
msgs[0].ReasoningContent,
|
||||
"need to inspect the file first",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreGetMessageCount(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue