-
-
Notifications
You must be signed in to change notification settings - Fork 522
Closed
Labels
bugSomething isn't workingSomething isn't working
Description
What version of Effect is running?
3.19.16
What steps can reproduce the bug?
dependencies:
@effect/ai 0.33.2
@effect/ai-openrouter 0.8.2
@effect/platform 0.94.4
@effect/platform-node 0.104.1
effect 3.19.16
import { Chat, Tool, Toolkit } from "@effect/ai"
import { OpenRouterClient, OpenRouterLanguageModel } from "@effect/ai-openrouter"
import { FetchHttpClient } from "@effect/platform"
import { NodeRuntime } from "@effect/platform-node"
import { Config, Console, Effect, Layer, Schema, Stream } from "effect"
const program = Effect.gen(function*() {
const chat = yield* Chat.empty
let resp = yield* chat.streamText({
prompt: "Tell me a dad joke"
})
.pipe(
Stream.runForEach(Console.log)
)
const history = yield* chat.exportJson
yield* Console.log(history)
})
const GlmModel = OpenRouterLanguageModel.model("z-ai/glm-4.7-flash")
const OpenRouterLive = OpenRouterClient.layerConfig({
apiKey: Config.redacted("OPENROUTER_API_KEY")
}).pipe(Layer.provide(FetchHttpClient.layer))
const AiLayer = Layer.provideMerge(GlmModel, OpenRouterLive)
const main = program.pipe(Effect.provide(AiLayer))
NodeRuntime.runMain(main)What is the expected behavior?
Each reasoning delta part is only emitted on the stream once
What do you see instead?
Every reasoning delta part is emitted on the stream twice, once with metadata and once without, e.g. Not sure about other part types, but text seems to be unaffected.
{
type: 'reasoning-delta',
id: '0',
delta: ' one',
metadata: { openrouter: undefined },
'~effect/ai/Content/Part': '~effect/ai/Content/Part'
}
{
type: 'reasoning-delta',
id: '0',
delta: ' one',
metadata: { openrouter: { type: 'reasoning', signature: undefined } },
'~effect/ai/Content/Part': '~effect/ai/Content/Part'
}When I export the chat history, you can see that each token is duplicated in the reasoning prompt
{"content":[{"role":"user","content":"Tell me a dad joke","options":{}},{"role":"assistant","content":[{"type":"reasoning","text":"11.. ** **AnAnalyzealyze the the Request Request:**:** The The user user wants wants a a \" \"daddad joke joke.\"\n\n.\"\n\n22.. ** **IdentIdentifyify the the Persona Persona/T/Toneone::**\n**\n * * \" \"DDadad joke joke\"\" characteristics characteristics:: pun punss,, gro groanan-ind-inducingucing humor humor,, situ situationalational irony irony,, corn cornyy,, wholesome wholesome,, often often involves involves food food or or simple simple word wordplayplay.\n.\n * * Tone Tone:: Play Playfulful,, slightly slightly self self-de-deprecprecatingating,, enthusiastic enthusiastic delivery delivery ( (eveneven if if just just text text).\n\n).\n\n33.. ** **BrainBrainstormstorminging Options Options::**\n**\n * * * *OptionOption 11 ( (FoodFood):):** Why Why did did the the tomato tomato turn turn red red?? Because Because it it saw saw the the salad salad dressing dressing.\n.\n * * * *OptionOption 22 ( (PPunun):):** I I'm'm reading reading a a book book about about anti anti-gr-gravityavity.. It It's's impossible impossible to to put put down down.\n.\n * * * *OptionOption 33 ( (WordWordplayplay):):** I I used used to to be be a a baker baker,, but but I I couldn couldn't't make make enough enough dough dough.\n.\n * * * *OptionOption 44 ( (ClassicClassic Dad Dad):):** Parallel Parallel lines lines have have so so much much in in common common.. It It’s’s a a shame shame they they’ll’ll never never meet meet.\n.\n * * * *OptionOption 55 ( (KnKnockock-k-knocknock):):** Knock Knock knock knock.. Who Who's's there there?? Let Letttuceuce.. Let Letttuceuce who who?? Let Letttuceuce in in,, it it's's cold cold out out here here!\n\n!\n\n44.. ** **SelectSelectinging the the Best Best Option Option::**\n**\n * * Option Option 11 is is classic classic.\n.\n * * Option Option 22 is is clever clever.\n.\n * * Option Option 33 is is a a bit bit of of a a standard standard gro groaneraner.\n.\n * * * *SelectionSelection:*:* \" \"ParallelParallel lines lines\"\" is is a a strong strong contender contender because because it it feels feels very very \" \"mathmath teacher teacher dad dad,\",\" but but \" \"ReadingReading a a book book about about anti anti-gr-gravityavity\"\" is is also also excellent excellent.\n.\n * * Let Let's's go go with with a a classic classic pun pun that that works works well well in in text text.\n\n.\n\n * *DraftDraft:*:* Why Why do do sc scubauba divers divers fall fall backwards backwards out out of of the the boat boat?? Because Because if if they they fell fell forwards forwards,, they they'd'd still still be be in in the the boat boat.\n\n.\n\n * *AlternativeAlternative Draft Draft ( (ShortShorterer/P/Punchunchierier):):** I I'm'm on on a a seafood seafood diet diet.. I I see see food food and and I I eat eat it it.\n\n.\n\n * *AlternativeAlternative Draft Draft ( (GroGroanan factor factor):):** What What do do you you call call a a fake fake nood noodlele?? An An imp impastaasta.\n\n.\n\n * *LetLet's's go go with with the the \" \"FakeFake nood noodlele\"\" one one or or the the \" \"SeaSeafoodfood diet diet\"\" one one.. The The \" \"FakeFake nood noodlele\"\" is is a a very very solid solid dad dad joke joke.*.* Let Let's's try try one one more more slightly slightly more more situ situationalational one one.\n\n.\n\n * *SelectionSelection:*:* \" \"II have have a a ste steplpladderadder.. I I never never knew knew my my real real ladder ladder.\"\n\n.\"\n\n * *LetLet's's go go with with the the \" \"SeaSeafoodfood diet diet\"\" one one.. It It's's universally universally understood understood and and hits hits the the gro groanan button button perfectly perfectly.*\n\n.*\n\n55.. ** **FinalFinal Polish Polish:**:** Ensure Ensure the the format format clearly clearly states states the the joke joke.\n\n.\n\n * *OutputOutput:*:* I I'm'm on on a a seafood seafood diet diet.. I I see see food food and and I I eat eat it it.\n\n.\n\n66.. ** **WaitWait,, let let me me give give two two in in case case the the first first one one is is too too short short..**\n**\n * * J Jokeoke 11:: I I'm'm on on a a seafood seafood diet diet.. I I see see food food and and I I eat eat it it.\n.\n * * J Jokeoke 22:: Parallel Parallel lines lines have have so so much much in in common common.. It It’s’s a a shame shame they they’ll’ll never never meet meet.\n\n.\n\n * *DecisionDecision:*:* I I'll'll give give one one really really good good one one,, as as usually usually requested requested.\n\n.\n\n * *ChChosenosen J Jokeoke:*:* \" \"II'm'm on on a a seafood seafood diet diet.. I I see see food food and and I I eat eat it it.\".\"","options":{}},{"type":"text","text":"I'm on a seafood diet. I see food and I eat it.","options":{}}],"options":{}}]}Additional information
Here is an example SSE event I obtained running this model on openrouter through Curl:
curl https://openrouter.ai/api/v1/chat/completions \
-N \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "z-ai/glm-4.7-flash",
"stream": true,
"messages": [
{ "role": "user", "content": "Tell me a dad joke." }
]
}'data: {"id":"gen-1770899608-YZYzehp0V5DKk6DK7WzY","provider":"Phala","model":"z-ai/glm-4.7-flash","object":"chat.completion.chunk","created":1770899608,"choices":[{"index":0,"delta":{"role":"assistant","content":"","reasoning":" provide","reasoning_details":[{"type":"reasoning.text","text":" provide","format":"unknown","index":0}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
I believe the issue is around this code (starting line 799)
if (Predicate.isNotNullable(delta.reasoning) && delta.reasoning.length > 0) {
emitReasoningPart(delta.reasoning)
// ^^ part is emitted *without* metadata, since it contains the reasoning field
}
if (Predicate.isNotNullable(delta.reasoning_details) && delta.reasoning_details.length > 0) {
for (const detail of delta.reasoning_details) {
switch (detail.type) {
// ...
case "reasoning.text": {
if (Predicate.isNotUndefined(detail.text) && detail.text.length > 0) {
emitReasoningPart(detail.text, {
type: "reasoning",
signature: detail.signature
})
// ^^ part is emitted a second time *with* metadata, since it contains reasoning details
}
break
}
}
}
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working