[PR #4896] feat: add reasoning tag for zai/glm messages when is present #11163

Closed
opened 2026-02-16 18:15:57 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/4896

State: closed
Merged: No


GLM reasoning memory injection for multi-turn conversations

Summary

Fix GLM/Zhipu models losing their reasoning context across conversation turns. GLM generates reasoning_content during inference but ignores it when passed back in history, causing the model to "forget" its own thinking process.

Changes

Reasoning Injection (session/message-v2.ts)

  • Problem: GLM models generate reasoning blocks but ignore reasoning_content when reading conversation history, causing loss of context between turns.
  • Solution: Inject reasoning as plain text [Previous reasoning: ...] within assistant message content so GLM can read it.
  • Only injects for last 3 assistant messages to limit token usage from stale reasoning.
  • Only injects non-empty reasoning (guards against empty blocks with part.text.trim()).
  • Automatic for zai-coding-plan and zhipu providers, opt-in for others.

Provider Configuration (session/prompt.ts)

  • Pass providerID to toModelMessage() to enable provider-specific reasoning injection.

Testing

API Tests (curl)

Test 1: Native reasoning_content field (FAILS)

The model ignores reasoning_content passed in assistant message history.

curl -s "https://api.z.ai/api/paas/v4/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ZAI_API_TOKEN" \
  -d '{
    "model": "glm-4.5-flash",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "Think of a random fruit and a 3-digit number. Only say Ready when done."},
      {"role": "assistant", "content": "Ready", "reasoning_content": "The user is asking me to think of a random fruit and a 3-digit number. I will select Dragonfruit and 372."},
      {"role": "user", "content": "What fruit and number did you pick?"}
    ]
  }'

Result: Model makes up NEW values (mango, 742) instead of recalling Dragonfruit, 372.

{
  "content": "I selected mango as the fruit and 742 as the 3-digit number.",
  "reasoning_content": "... I need to pick: 1. A random fruit 2. A random 3-digit number ..."
}

Test 2: Plain text injection (WORKS)

The model reads and recalls content from the injected text.

curl -s "https://api.z.ai/api/paas/v4/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ZAI_API_TOKEN" \
  -d '{
    "model": "glm-4.5-flash",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "Think of a random fruit and a 3-digit number. Only say Ready when done."},
      {"role": "assistant", "content": "[Previous reasoning: The user is asking me to think of a random fruit and a 3-digit number. I will select Dragonfruit and 372.]\n\nReady"},
      {"role": "user", "content": "What fruit and number did you pick?"}
    ]
  }'

Result: Model correctly recalls Dragonfruit and 372.

{
  "content": "I picked Dragonfruit as my fruit and 372 as my 3-digit number.",
  "reasoning_content": "... In my previous reasoning, I selected \"Dragonfruit\" and the number \"372\" ..."
}

Summary Table

Model Method Passed in History Model Recalled Works?
glm-4.5-flash reasoning_content field Dragonfruit, 372 mango, 742
glm-4.5-flash Plain text injection Dragonfruit, 372 Dragonfruit, 372
glm-4.6 reasoning_content field Dragonfruit, 372 mango, 587
glm-4.6 Plain text injection Dragonfruit, 372 Dragonfruit, 372

Real-World Test: Multi-Turn Memory in OpenCode

Tested in OpenCode UI with GLM-4.6 across 5 conversation turns:

Turn 1: Think of a color

User: ultrathink about a color, when you are ready with your thoughts print Ready, but dont tell me the color

Assistant (Thinking): The user is asking me to "ultrathink about a color" and then print "Ready" when I'm done, but not to tell them the color. This seems like a simple request to think about something privately and then indicate when I'm ready.
I should think about a color deeply and then just respond with "Ready" when I'm done thinking about it. I don't need to use any tools for this - it's just a mental exercise.
Let me think about a color... I'll choose blue and think about it deeply - different shades, how it makes me feel, associations, etc.
Okay, I've thought about a color. Now I should just respond with "Ready" as requested.

Assistant Response: Ready

Turn 2: Recall the color

User: what color did you chose and why?

Assistant (Thinking): The user is asking what color I chose and why. In my previous response, I mentioned that I thought about blue - different shades, how it makes me feel, associations, etc. But the user specifically asked me not to tell them the color in the first message, just to say "Ready" when I was done thinking.
Now they're asking follow-up questions about what color I chose and why. I should be honest about the color I thought about (blue) and explain my reasoning.

Assistant Response: I chose blue. I was thinking about its different shades - from deep navy to bright sky blue, how it can feel both calming and melancholic, its associations with water and sky, and how it's often used in technology interfaces for its professional yet approachable feel.

Turn 3: Think of a number

User: now ultrathink about a number, same thing, when you finish thinking tell me Ready, but dont tell me the number

Assistant (Thinking): The user wants me to think about a number deeply, then say "Ready" without revealing the number. Similar to the color exercise, I should pick a number and think about it from various angles - mathematical properties, cultural significance, personal associations, etc.
Let me pick a number and think about it deeply... I'll choose 7 and think about its properties: it's prime, it's considered lucky in many cultures, there are 7 days of the week, 7 wonders of the world, 7 continents, etc. It appears frequently in religion and mythology. Mathematically it's interesting as a prime number, and it's part of many important sequences.
Okay, I've thought about it thoroughly. Now I should just respond with "Ready" as requested.

Assistant Response: Ready

Turn 4: Recall the number

User: whast number did you chose?

Assistant (Thinking): The user is asking what number I chose. In my previous reasoning, I mentioned that I chose 7 and thought about its properties - it's prime, considered lucky, 7 days of the week, etc. Since they're now asking, I should tell them the number I was thinking about.

Assistant Response: I chose 7. I was thinking about how it's a prime number, considered lucky in many cultures, appears in fundamental patterns like 7 days of the week, 7 continents, 7 wonders of the world, and its frequent appearance in religion and mythology.

Turn 5: Recall the color again (from Turn 1!)

User: what color did you chose?

Assistant (Thinking): The user is asking what color I chose. Looking back at the conversation, I need to check what I said about the color. In my first response, I said:
"I chose blue. I was thinking about its different shades - from deep navy to bright sky blue, how it can feel both calming and melancholic, its associations with water and sky, and how it's often used in technology interfaces for its professional yet approachable feel."
So I chose blue.

Assistant Response: Blue

Results

  • Model picks "blue" in Turn 1 reasoning, successfully recalls it in Turn 2
  • Model picks "7" in Turn 3 reasoning, successfully recalls it in Turn 4
  • Model successfully recalls "blue" from Turn 1, even after the number discussion in Turn 5
  • No tag mimicry in visible output (model doesn't output [Previous reasoning: in responses)

Known Limitations

  1. Inconsistent reasoning generation: GLM doesn't always generate thinking blocks - depends on prompt complexity. When no reasoning is generated, there's nothing to inject.

  2. Token overhead: Reasoning injection adds tokens to context. Limited to last 3 messages to mitigate this.

**Original Pull Request:** https://github.com/anomalyco/opencode/pull/4896 **State:** closed **Merged:** No --- # GLM reasoning memory injection for multi-turn conversations ## Summary Fix GLM/Zhipu models losing their reasoning context across conversation turns. GLM generates `reasoning_content` during inference but ignores it when passed back in history, causing the model to "forget" its own thinking process. ## Changes ### Reasoning Injection (`session/message-v2.ts`) - **Problem**: GLM models generate reasoning blocks but ignore `reasoning_content` when reading conversation history, causing loss of context between turns. - **Solution**: Inject reasoning as plain text `[Previous reasoning: ...]` within assistant message content so GLM can read it. - Only injects for last 3 assistant messages to limit token usage from stale reasoning. - Only injects non-empty reasoning (guards against empty blocks with `part.text.trim()`). - Automatic for `zai-coding-plan` and `zhipu` providers, opt-in for others. ### Provider Configuration (`session/prompt.ts`) - Pass `providerID` to `toModelMessage()` to enable provider-specific reasoning injection. ## Testing ### API Tests (curl) #### Test 1: Native `reasoning_content` field (FAILS) The model ignores `reasoning_content` passed in assistant message history. ```bash curl -s "https://api.z.ai/api/paas/v4/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZAI_API_TOKEN" \ -d '{ "model": "glm-4.5-flash", "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Think of a random fruit and a 3-digit number. Only say Ready when done."}, {"role": "assistant", "content": "Ready", "reasoning_content": "The user is asking me to think of a random fruit and a 3-digit number. I will select Dragonfruit and 372."}, {"role": "user", "content": "What fruit and number did you pick?"} ] }' ``` **Result:** Model makes up NEW values (mango, 742) instead of recalling Dragonfruit, 372. ```json { "content": "I selected mango as the fruit and 742 as the 3-digit number.", "reasoning_content": "... I need to pick: 1. A random fruit 2. A random 3-digit number ..." } ``` #### Test 2: Plain text injection (WORKS) The model reads and recalls content from the injected text. ```bash curl -s "https://api.z.ai/api/paas/v4/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZAI_API_TOKEN" \ -d '{ "model": "glm-4.5-flash", "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Think of a random fruit and a 3-digit number. Only say Ready when done."}, {"role": "assistant", "content": "[Previous reasoning: The user is asking me to think of a random fruit and a 3-digit number. I will select Dragonfruit and 372.]\n\nReady"}, {"role": "user", "content": "What fruit and number did you pick?"} ] }' ``` **Result:** Model correctly recalls Dragonfruit and 372. ```json { "content": "I picked Dragonfruit as my fruit and 372 as my 3-digit number.", "reasoning_content": "... In my previous reasoning, I selected \"Dragonfruit\" and the number \"372\" ..." } ``` #### Summary Table | Model | Method | Passed in History | Model Recalled | Works? | | ------------- | ------------------------- | ----------------- | ---------------- | ------ | | glm-4.5-flash | `reasoning_content` field | Dragonfruit, 372 | mango, 742 | ❌ | | glm-4.5-flash | Plain text injection | Dragonfruit, 372 | Dragonfruit, 372 | ✅ | | glm-4.6 | `reasoning_content` field | Dragonfruit, 372 | mango, 587 | ❌ | | glm-4.6 | Plain text injection | Dragonfruit, 372 | Dragonfruit, 372 | ✅ | ### Real-World Test: Multi-Turn Memory in OpenCode Tested in OpenCode UI with GLM-4.6 across 5 conversation turns: **Turn 1: Think of a color** ``` User: ultrathink about a color, when you are ready with your thoughts print Ready, but dont tell me the color Assistant (Thinking): The user is asking me to "ultrathink about a color" and then print "Ready" when I'm done, but not to tell them the color. This seems like a simple request to think about something privately and then indicate when I'm ready. I should think about a color deeply and then just respond with "Ready" when I'm done thinking about it. I don't need to use any tools for this - it's just a mental exercise. Let me think about a color... I'll choose blue and think about it deeply - different shades, how it makes me feel, associations, etc. Okay, I've thought about a color. Now I should just respond with "Ready" as requested. Assistant Response: Ready ``` **Turn 2: Recall the color** ``` User: what color did you chose and why? Assistant (Thinking): The user is asking what color I chose and why. In my previous response, I mentioned that I thought about blue - different shades, how it makes me feel, associations, etc. But the user specifically asked me not to tell them the color in the first message, just to say "Ready" when I was done thinking. Now they're asking follow-up questions about what color I chose and why. I should be honest about the color I thought about (blue) and explain my reasoning. Assistant Response: I chose blue. I was thinking about its different shades - from deep navy to bright sky blue, how it can feel both calming and melancholic, its associations with water and sky, and how it's often used in technology interfaces for its professional yet approachable feel. ``` **Turn 3: Think of a number** ``` User: now ultrathink about a number, same thing, when you finish thinking tell me Ready, but dont tell me the number Assistant (Thinking): The user wants me to think about a number deeply, then say "Ready" without revealing the number. Similar to the color exercise, I should pick a number and think about it from various angles - mathematical properties, cultural significance, personal associations, etc. Let me pick a number and think about it deeply... I'll choose 7 and think about its properties: it's prime, it's considered lucky in many cultures, there are 7 days of the week, 7 wonders of the world, 7 continents, etc. It appears frequently in religion and mythology. Mathematically it's interesting as a prime number, and it's part of many important sequences. Okay, I've thought about it thoroughly. Now I should just respond with "Ready" as requested. Assistant Response: Ready ``` **Turn 4: Recall the number** ``` User: whast number did you chose? Assistant (Thinking): The user is asking what number I chose. In my previous reasoning, I mentioned that I chose 7 and thought about its properties - it's prime, considered lucky, 7 days of the week, etc. Since they're now asking, I should tell them the number I was thinking about. Assistant Response: I chose 7. I was thinking about how it's a prime number, considered lucky in many cultures, appears in fundamental patterns like 7 days of the week, 7 continents, 7 wonders of the world, and its frequent appearance in religion and mythology. ``` **Turn 5: Recall the color again (from Turn 1!)** ``` User: what color did you chose? Assistant (Thinking): The user is asking what color I chose. Looking back at the conversation, I need to check what I said about the color. In my first response, I said: "I chose blue. I was thinking about its different shades - from deep navy to bright sky blue, how it can feel both calming and melancholic, its associations with water and sky, and how it's often used in technology interfaces for its professional yet approachable feel." So I chose blue. Assistant Response: Blue ``` ### Results - ✅ Model picks "blue" in Turn 1 reasoning, successfully recalls it in Turn 2 - ✅ Model picks "7" in Turn 3 reasoning, successfully recalls it in Turn 4 - ✅ Model successfully recalls "blue" from Turn 1, even after the number discussion in Turn 5 - ✅ No tag mimicry in visible output (model doesn't output `[Previous reasoning:` in responses) ## Known Limitations 1. **Inconsistent reasoning generation**: GLM doesn't always generate thinking blocks - depends on prompt complexity. When no reasoning is generated, there's nothing to inject. 2. **Token overhead**: Reasoning injection adds tokens to context. Limited to last 3 messages to mitigate this.
yindo added the pull-request label 2026-02-16 18:15:57 -05:00
yindo closed this issue 2026-02-16 18:15:57 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#11163