mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-25 05:52:34 +00:00
fix(tui): strip <think>…</think> tags from assistant content and route to reasoning panel
Models that emit reasoning inline as <think>/<reasoning>/<thinking>/<thought>/
<REASONING_SCRATCHPAD> tags in the content field (rather than a separate API
reasoning channel) had the raw tags + inner content shown twice: once as body
text with literal <think> markers, and again in the thinking panel when the
reasoning field was populated.
Port v1's tag set to lib/reasoning.ts with a splitReasoning(text) helper that
returns { reasoning, text }. Applied in three spots:
- scheduleStreaming: strips tags from the live streaming view so the user
never sees <think> mid-turn.
- flushStreamingSegment: when a tool interrupts assistant output mid-turn,
the saved segment is the stripped text; extracted reasoning promotes to
reasoningText if the API channel hasn't already populated it.
- recordMessageComplete: final message text is split, extracted reasoning
merges with any existing reasoning (API channel wins on conflicts so we
don't double-count when both are present).
This commit is contained in:
parent
37cba82bfc
commit
4caf6c23dd
3 changed files with 127 additions and 8 deletions
50
ui-tui/src/lib/reasoning.ts
Normal file
50
ui-tui/src/lib/reasoning.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
const TAGS = ['think', 'reasoning', 'thinking', 'thought', 'REASONING_SCRATCHPAD'] as const
|
||||
|
||||
export interface SplitReasoning {
|
||||
reasoning: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export function splitReasoning(input: string): SplitReasoning {
|
||||
let text = input
|
||||
const reasoning: string[] = []
|
||||
|
||||
for (const tag of TAGS) {
|
||||
const paired = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>\\s*`, 'gi')
|
||||
text = text.replace(paired, (_m, inner: string) => {
|
||||
const trimmed = inner.trim()
|
||||
|
||||
if (trimmed) {
|
||||
reasoning.push(trimmed)
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
const unclosed = new RegExp(`<${tag}>([\\s\\S]*)$`, 'i')
|
||||
text = text.replace(unclosed, (_m, inner: string) => {
|
||||
const trimmed = inner.trim()
|
||||
|
||||
if (trimmed) {
|
||||
reasoning.push(trimmed)
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
reasoning: reasoning.join('\n\n').trim(),
|
||||
text: text.trim()
|
||||
}
|
||||
}
|
||||
|
||||
export const hasReasoningTag = (input: string) => {
|
||||
for (const tag of TAGS) {
|
||||
if (input.includes(`<${tag}>`)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue