mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: remove reasoning title extraction from chat pipeline (#22926)
Removes the backend and frontend logic that extracted compact titles from reasoning/thinking blocks. The `Title` field on `ChatMessagePart` remains for other part types (e.g. source), but reasoning blocks no longer have titles derived from first-line markdown bold text or provider metadata summaries. **Backend:** - Remove `ReasoningTitleFromFirstLine`, `reasoningTitleFromContent`, `reasoningSummaryTitle`, `compactReasoningSummaryTitle`, and `reasoningSummaryHeadline` from chatprompt - Simplify `marshalContentBlock` to plain `json.Marshal` (no title injection) - Remove title tracking maps and `setReasoningTitleFromText` from chatloop stream processing - Remove `reasoningStoredTitle` from db2sdk - Remove related tests from db2sdk_test **Frontend:** - Remove `mergeThinkingTitles` from blockUtils - Simplify `appendTextBlock` to always merge consecutive thinking blocks - Remove `applyStreamThinkingTitle` from streamState - Simplify reasoning/thinking stream handler to ignore title-only parts - Update tests accordingly Net: **-487 lines / +42 lines**
This commit is contained in:
@@ -1165,10 +1165,7 @@ func chatMessageParts(role string, raw pqtype.NullRawMessage) ([]codersdk.ChatMe
|
||||
continue
|
||||
}
|
||||
if i < len(rawBlocks) {
|
||||
switch part.Type {
|
||||
case codersdk.ChatMessagePartTypeReasoning:
|
||||
part.Title = reasoningStoredTitle(rawBlocks[i])
|
||||
case codersdk.ChatMessagePartTypeFile:
|
||||
if part.Type == codersdk.ChatMessagePartTypeFile {
|
||||
if fid, err := chatprompt.ExtractFileID(rawBlocks[i]); err == nil {
|
||||
part.FileID = uuid.NullUUID{UUID: fid, Valid: true}
|
||||
}
|
||||
@@ -1267,22 +1264,6 @@ func parseToolResults(raw pqtype.NullRawMessage) ([]toolResultRow, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func reasoningStoredTitle(raw json.RawMessage) string {
|
||||
var envelope struct {
|
||||
Type string `json:"type"`
|
||||
Data struct {
|
||||
Title string `json:"title"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &envelope); err != nil {
|
||||
return ""
|
||||
}
|
||||
if !strings.EqualFold(envelope.Type, string(fantasy.ContentTypeReasoning)) {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(envelope.Data.Title)
|
||||
}
|
||||
|
||||
func contentBlockToPart(block fantasy.Content) codersdk.ChatMessagePart {
|
||||
switch value := block.(type) {
|
||||
case fantasy.TextContent:
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"charm.land/fantasy"
|
||||
fantasyopenai "charm.land/fantasy/providers/openai"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sqlc-dev/pqtype"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -438,82 +437,6 @@ func TestAIBridgeInterception(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessage_ReasoningPartWithoutPersistedTitleIsEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assistantContent, err := json.Marshal([]fantasy.Content{
|
||||
fantasy.ReasoningContent{
|
||||
Text: "Plan migration",
|
||||
ProviderMetadata: fantasy.ProviderMetadata{
|
||||
fantasyopenai.Name: &fantasyopenai.ResponsesReasoningMetadata{
|
||||
ItemID: "reasoning-1",
|
||||
Summary: []string{"Plan migration"},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
message := db2sdk.ChatMessage(database.ChatMessage{
|
||||
ID: 1,
|
||||
ChatID: uuid.New(),
|
||||
CreatedAt: time.Now(),
|
||||
Role: string(fantasy.MessageRoleAssistant),
|
||||
Content: pqtype.NullRawMessage{
|
||||
RawMessage: assistantContent,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
|
||||
require.Len(t, message.Content, 1)
|
||||
require.Equal(t, codersdk.ChatMessagePartTypeReasoning, message.Content[0].Type)
|
||||
require.Equal(t, "Plan migration", message.Content[0].Text)
|
||||
require.Empty(t, message.Content[0].Title)
|
||||
}
|
||||
|
||||
func TestChatMessage_ReasoningPartPrefersPersistedTitle(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
reasoningContent, err := json.Marshal(fantasy.ReasoningContent{
|
||||
Text: "Verify schema updates, then apply changes in order.",
|
||||
ProviderMetadata: fantasy.ProviderMetadata{
|
||||
fantasyopenai.Name: &fantasyopenai.ResponsesReasoningMetadata{
|
||||
ItemID: "reasoning-1",
|
||||
Summary: []string{
|
||||
"**Metadata-derived title**\n\nLonger explanation.",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var envelope map[string]any
|
||||
require.NoError(t, json.Unmarshal(reasoningContent, &envelope))
|
||||
dataValue, ok := envelope["data"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
dataValue["title"] = "Persisted stream title"
|
||||
|
||||
encodedReasoning, err := json.Marshal(envelope)
|
||||
require.NoError(t, err)
|
||||
assistantContent, err := json.Marshal([]json.RawMessage{encodedReasoning})
|
||||
require.NoError(t, err)
|
||||
|
||||
message := db2sdk.ChatMessage(database.ChatMessage{
|
||||
ID: 1,
|
||||
ChatID: uuid.New(),
|
||||
CreatedAt: time.Now(),
|
||||
Role: string(fantasy.MessageRoleAssistant),
|
||||
Content: pqtype.NullRawMessage{
|
||||
RawMessage: assistantContent,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
|
||||
require.Len(t, message.Content, 1)
|
||||
require.Equal(t, codersdk.ChatMessagePartTypeReasoning, message.Content[0].Type)
|
||||
require.Equal(t, "Persisted stream title", message.Content[0].Title)
|
||||
}
|
||||
|
||||
func TestChatQueuedMessage_ParsesUserContentParts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user