-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix: parse gpt-oss special token format in LM Studio responses #6740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -100,9 +100,24 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan | |||||||||||||||||||
| const delta = chunk.choices[0]?.delta | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (delta?.content) { | ||||||||||||||||||||
| assistantText += delta.content | ||||||||||||||||||||
| for (const processedChunk of matcher.update(delta.content)) { | ||||||||||||||||||||
| yield processedChunk | ||||||||||||||||||||
| // Check if this is a gpt-oss model with special token format | ||||||||||||||||||||
| const isGptOss = this.getModel().id?.toLowerCase().includes("gpt-oss") | ||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performance consideration: Since the model doesn't change during streaming, could we move this gpt-oss check outside the loop to avoid repeated string operations on every chunk?
Suggested change
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| if (isGptOss && delta.content.includes("<|") && delta.content.includes("|>")) { | ||||||||||||||||||||
| // Parse gpt-oss special token format | ||||||||||||||||||||
| // Format: <|start|>assistant<|channel|>commentary to=read_file <|constrain|>json<|message|>{"args":[...]} | ||||||||||||||||||||
| const cleanedContent = this.parseGptOssFormat(delta.content) | ||||||||||||||||||||
| if (cleanedContent) { | ||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When |
||||||||||||||||||||
| assistantText += cleanedContent | ||||||||||||||||||||
| for (const processedChunk of matcher.update(cleanedContent)) { | ||||||||||||||||||||
| yield processedChunk | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| assistantText += delta.content | ||||||||||||||||||||
| for (const processedChunk of matcher.update(delta.content)) { | ||||||||||||||||||||
| yield processedChunk | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
@@ -169,6 +184,31 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan | |||||||||||||||||||
| ) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Parse gpt-oss special token format | ||||||||||||||||||||
| * Format example: <|start|>assistant<|channel|>commentary to=read_file <|constrain|>json<|message|>{"args":[...]} | ||||||||||||||||||||
| * We want to extract just the actual message content | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| private parseGptOssFormat(content: string): string { | ||||||||||||||||||||
| // Remove all special tokens and extract the actual message | ||||||||||||||||||||
| // Pattern: <|token|> where token can be any word | ||||||||||||||||||||
| const specialTokenPattern = /<\|[^|]+\|>/g | ||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The regex pattern |
||||||||||||||||||||
|
|
||||||||||||||||||||
| // First, check if this contains the message token | ||||||||||||||||||||
| const messageMatch = content.match(/<\|message\|>(.+)$/s) | ||||||||||||||||||||
| if (messageMatch) { | ||||||||||||||||||||
| // Extract content after <|message|> token | ||||||||||||||||||||
| return messageMatch[1].trim() | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Otherwise, just remove all special tokens | ||||||||||||||||||||
| const cleaned = content.replace(specialTokenPattern, " ").trim() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Also clean up any "to=function_name" patterns that might remain | ||||||||||||||||||||
| const functionPattern = /\s*to=\w+\s*/g | ||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function pattern |
||||||||||||||||||||
| return cleaned.replace(functionPattern, " ").trim() | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export async function getLmStudioModels(baseUrl = "http://localhost:1234") { | ||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding test cases for edge scenarios:
<|message|>tokens in a single chunk<|start)