-
Notifications
You must be signed in to change notification settings - Fork 362
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Backport M72] feat(webview): add support for <think> tags in Chat Me…
…ssage (#7209) RE: https://linear.app/sourcegraph/issue/CODY-4785 This PR adds a simple hack to support rendering <think> tags in the ChatMessageContent component. The <think> content is displayed in a collapsible details element, allowing users to view the AI's internal thought process. The MarkdownFromCody component is also updated to allow the <think> element. ## Test plan Ask Cody "how many files are there in the codebase? Enclose your thoughts inside <think> tags before answering" - Verify that <think> tags are properly extracted and displayed in the ChatMessageContent component - Ensure that the collapsible details element functions as expected, allowing users to view the think content - Confirm that the MarkdownFromCody component correctly renders the <think> element Example: https://github.com/user-attachments/assets/0a5cff8f-1b08-48e4-9cf9-4fd6d13ef05a <br> Backport 710ac73 from #6845 Co-authored-by: Beatrix <[email protected]>
- Loading branch information
1 parent
99ff04a
commit bd9fee0
Showing
9 changed files
with
300 additions
and
5 deletions.
There are no files selected for viewing
98 changes: 98 additions & 0 deletions
98
lib/shared/src/sourcegraph-api/completions/CompletionsResponseBuilder.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { describe, expect, it } from 'vitest' | ||
|
||
import { CompletionsResponseBuilder } from './CompletionsResponseBuilder' | ||
|
||
interface CompletionsResponseTestCase { | ||
name: string | ||
url: string | ||
steps: { | ||
deltaText?: string | ||
completion?: string | ||
thinking?: string | ||
expected: string | ||
}[] | ||
} | ||
|
||
describe('CompletionsResponseBuilder', () => { | ||
const testCases: CompletionsResponseTestCase[] = [ | ||
{ | ||
name: 'API v1 - Full replacement mode', | ||
url: 'https://sourcegraph.com/.api/completions/stream?api-version=1', | ||
steps: [ | ||
{ | ||
completion: 'Direct', | ||
expected: 'Direct', | ||
}, | ||
{ | ||
completion: ' Response', | ||
expected: ' Response', | ||
}, | ||
], | ||
}, | ||
{ | ||
name: 'API v2 - Incremental mode', | ||
url: 'https://sourcegraph.com/.api/completions/stream?api-version=2', | ||
steps: [ | ||
{ | ||
deltaText: undefined, | ||
expected: '', | ||
}, | ||
{ | ||
deltaText: undefined, | ||
expected: '', | ||
}, | ||
{ | ||
deltaText: 'Starting response', | ||
expected: 'Starting response', | ||
}, | ||
], | ||
}, | ||
{ | ||
name: 'API v2 - Incremental mode with thinking steps', | ||
url: 'https://sourcegraph.com/.api/completions/stream?api-version=2', | ||
steps: [ | ||
{ | ||
thinking: 'Analyzing...', | ||
expected: '<think>Analyzing...</think>\n', | ||
}, | ||
{ | ||
thinking: 'Refining...', | ||
expected: '<think>Analyzing...Refining...</think>\n', | ||
}, | ||
{ | ||
deltaText: 'Better response', | ||
expected: '<think>Analyzing...Refining...</think>\nBetter response', | ||
}, | ||
], | ||
}, | ||
{ | ||
name: 'API v8 - Incremental mode with thinking steps', | ||
url: 'https://sourcegraph.com/.api/completions/stream?api-version=8', | ||
steps: [ | ||
{ | ||
thinking: 'Step 1...', | ||
deltaText: 'Hello', | ||
expected: '<think>Step 1...</think>\nHello', | ||
}, | ||
{ | ||
thinking: 'Step 2...', | ||
deltaText: ' World', | ||
expected: '<think>Step 1...Step 2...</think>\nHello World', | ||
}, | ||
], | ||
}, | ||
] | ||
|
||
for (const testCase of testCases) { | ||
describe(testCase.name, () => { | ||
it('processes completion steps correctly', () => { | ||
const builder = CompletionsResponseBuilder.fromUrl(testCase.url) | ||
for (const step of testCase.steps) { | ||
builder.nextThinking(step.thinking ?? undefined) | ||
const result = builder.nextCompletion(step.completion, step.deltaText) | ||
expect(result).toBe(step.expected) | ||
} | ||
}) | ||
}) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { describe, expect, it } from 'vitest' | ||
import { extractThinkContent } from './utils' | ||
|
||
describe('extractThinkContent', () => { | ||
it('extracts content from complete think tags at the start', () => { | ||
const input = '<think>Planning steps</think>Here is the code' | ||
const result = extractThinkContent(input) | ||
|
||
expect(result).toEqual({ | ||
displayContent: 'Here is the code', | ||
thinkContent: 'Planning steps', | ||
isThinking: false, | ||
}) | ||
}) | ||
|
||
it('ignores think tags that do not start at the beginning', () => { | ||
const input = 'Code here<think>Step 2</think>More code' | ||
const result = extractThinkContent(input) | ||
|
||
expect(result).toEqual({ | ||
displayContent: 'Code here<think>Step 2</think>More code', | ||
thinkContent: '', | ||
isThinking: false, | ||
}) | ||
}) | ||
|
||
it('handles unclosed think tag at the start', () => { | ||
const input = '<think>Incomplete thought' | ||
const result = extractThinkContent(input) | ||
|
||
expect(result).toEqual({ | ||
displayContent: '', | ||
thinkContent: 'Incomplete thought', | ||
isThinking: true, | ||
}) | ||
}) | ||
|
||
it('ignores unclosed think tag not at the start', () => { | ||
const input = 'Middle<think>Incomplete' | ||
const result = extractThinkContent(input) | ||
|
||
expect(result).toEqual({ | ||
displayContent: 'Middle<think>Incomplete', | ||
thinkContent: '', | ||
isThinking: false, | ||
}) | ||
}) | ||
|
||
it('returns empty think content for input without think tags', () => { | ||
const input = 'Regular content without think tags' | ||
const result = extractThinkContent(input) | ||
|
||
expect(result).toEqual({ | ||
displayContent: 'Regular content without think tags', | ||
thinkContent: '', | ||
isThinking: false, | ||
}) | ||
}) | ||
|
||
it('keeps isThinking true when think tag is closed but no content follows', () => { | ||
const input = '<think>Completed thought</think>' | ||
const result = extractThinkContent(input) | ||
|
||
expect(result).toEqual({ | ||
displayContent: '', | ||
thinkContent: 'Completed thought', | ||
isThinking: true, | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters