openrouter-haystack 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/CHANGELOG.md +19 -4
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/LICENSE.txt +1 -1
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/PKG-INFO +3 -3
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/README.md +1 -1
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/pyproject.toml +1 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/src/haystack_integrations/components/generators/openrouter/chat/chat_generator.py +164 -30
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/tests/test_openrouter_chat_generator.py +327 -276
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/tests/test_openrouter_chat_generator_async.py +199 -184
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/.gitignore +0 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/examples/openrouter_with_structured_outputs.py +0 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/examples/openrouter_with_tools_example.py +0 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/pydoc/config_docusaurus.yml +0 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/src/haystack_integrations/components/generators/openrouter/__init__.py +0 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/src/haystack_integrations/components/generators/openrouter/chat/__init__.py +0 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/src/haystack_integrations/components/generators/py.typed +0 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/tests/__init__.py +0 -0
- {openrouter_haystack-1.2.0 → openrouter_haystack-1.3.0}/tests/conftest.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/openrouter-v1.2.0] - 2026-07-06
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- Support Haystack 3.0's lazy OpenAI client initialization in OpenAI-inheriting generators (#3536)
|
|
8
|
+
|
|
9
|
+
### 🧪 Testing
|
|
10
|
+
|
|
11
|
+
- Trust test modules under Haystack 3.0's deserialization allowlist (#3537)
|
|
12
|
+
- Make Tool/Agent serialization assertions version-agnostic for Haystack 2.x/3.x (#3533)
|
|
13
|
+
- Guard ToolInvoker imports so chat-generator tests run under Haystack 3.0 (#3535)
|
|
14
|
+
|
|
15
|
+
|
|
3
16
|
## [integrations/openrouter-v1.1.0] - 2026-06-08
|
|
4
17
|
|
|
5
18
|
### 🚀 Features
|
|
@@ -85,10 +98,6 @@
|
|
|
85
98
|
|
|
86
99
|
## [integrations/openrouter-v0.2.0] - 2025-07-01
|
|
87
100
|
|
|
88
|
-
### 🚀 Features
|
|
89
|
-
|
|
90
|
-
- Add OpenRouter integration (#1723)
|
|
91
|
-
|
|
92
101
|
### 🐛 Bug Fixes
|
|
93
102
|
|
|
94
103
|
- Fix openrouter types + add py.typed (#2029)
|
|
@@ -103,4 +112,10 @@
|
|
|
103
112
|
|
|
104
113
|
- Test: Remove `test_check_abnormal_completions` - already tested in Haystack (#1842)
|
|
105
114
|
|
|
115
|
+
## [integrations/openrouter-v0.1.0] - 2025-05-16
|
|
116
|
+
|
|
117
|
+
### 🚀 Features
|
|
118
|
+
|
|
119
|
+
- Add OpenRouter integration (#1723)
|
|
120
|
+
|
|
106
121
|
<!-- generated by git-cliff -->
|
|
@@ -58,7 +58,7 @@ APPENDIX: How to apply the Apache License to your work.
|
|
|
58
58
|
|
|
59
59
|
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
|
60
60
|
|
|
61
|
-
Copyright
|
|
61
|
+
Copyright 2023-present deepset GmbH
|
|
62
62
|
|
|
63
63
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
64
64
|
you may not use this file except in compliance with the License.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: openrouter-haystack
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/openrouter#readme
|
|
5
5
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
6
6
|
Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/openrouter
|
|
@@ -24,7 +24,7 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
# openrouter-haystack
|
|
25
25
|
|
|
26
26
|
[](https://pypi.org/project/openrouter-haystack)
|
|
27
|
-
[](https://pypi.org/project/
|
|
27
|
+
[](https://pypi.org/project/openrouter-haystack)
|
|
28
28
|
|
|
29
29
|
- [Integration page](https://haystack.deepset.ai/integrations/openrouter)
|
|
30
30
|
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/openrouter/CHANGELOG.md)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# openrouter-haystack
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/openrouter-haystack)
|
|
4
|
-
[](https://pypi.org/project/
|
|
4
|
+
[](https://pypi.org/project/openrouter-haystack)
|
|
5
5
|
|
|
6
6
|
- [Integration page](https://haystack.deepset.ai/integrations/openrouter)
|
|
7
7
|
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/openrouter/CHANGELOG.md)
|
|
@@ -2,24 +2,38 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
6
|
+
import inspect
|
|
5
7
|
import json
|
|
8
|
+
from dataclasses import replace
|
|
6
9
|
from typing import Any
|
|
7
10
|
|
|
8
11
|
from haystack import component, default_to_dict, logging
|
|
9
12
|
from haystack.components.generators.chat import OpenAIChatGenerator
|
|
10
|
-
from haystack.components.generators.chat.openai import
|
|
11
|
-
|
|
13
|
+
from haystack.components.generators.chat.openai import (
|
|
14
|
+
_check_finish_reason,
|
|
15
|
+
_convert_chat_completion_chunk_to_streaming_chunk,
|
|
16
|
+
)
|
|
17
|
+
from haystack.components.generators.utils import (
|
|
18
|
+
_convert_streaming_chunks_to_chat_message,
|
|
19
|
+
_normalize_messages,
|
|
20
|
+
_serialize_object,
|
|
21
|
+
)
|
|
12
22
|
from haystack.dataclasses import (
|
|
13
23
|
ChatMessage,
|
|
24
|
+
ComponentInfo,
|
|
14
25
|
ReasoningContent,
|
|
15
26
|
StreamingCallbackT,
|
|
27
|
+
StreamingChunk,
|
|
28
|
+
SyncStreamingCallbackT,
|
|
16
29
|
ToolCall,
|
|
17
30
|
select_streaming_callback,
|
|
18
31
|
)
|
|
19
32
|
from haystack.tools import ToolsType, _check_duplicate_tool_names, flatten_tools_or_toolsets, serialize_tools_or_toolset
|
|
20
33
|
from haystack.utils import serialize_callable
|
|
21
34
|
from haystack.utils.auth import Secret
|
|
22
|
-
from openai
|
|
35
|
+
from openai import AsyncStream, Stream
|
|
36
|
+
from openai.types.chat import ChatCompletion, ChatCompletionChunk, ParsedChatCompletion
|
|
23
37
|
from openai.types.chat.chat_completion import Choice
|
|
24
38
|
|
|
25
39
|
logger = logging.getLogger(__name__)
|
|
@@ -101,6 +115,109 @@ def _convert_openrouter_completion_to_chat_message(
|
|
|
101
115
|
return ChatMessage.from_assistant(text=text, tool_calls=tool_calls, meta=meta, reasoning=reasoning)
|
|
102
116
|
|
|
103
117
|
|
|
118
|
+
def _convert_openrouter_chunk_to_streaming_chunks(
|
|
119
|
+
chunk: ChatCompletionChunk, previous_chunks: list[StreamingChunk], component_info: ComponentInfo
|
|
120
|
+
) -> list[StreamingChunk]:
|
|
121
|
+
"""
|
|
122
|
+
Convert an OpenRouter streaming chunk to StreamingChunks, including reasoning content.
|
|
123
|
+
|
|
124
|
+
A `StreamingChunk` holds either reasoning or content/tool calls, so a delta carrying both is split into a
|
|
125
|
+
reasoning chunk followed by the content or tool call chunk.
|
|
126
|
+
"""
|
|
127
|
+
streaming_chunk = _convert_chat_completion_chunk_to_streaming_chunk(
|
|
128
|
+
chunk=chunk, previous_chunks=previous_chunks, component_info=component_info
|
|
129
|
+
)
|
|
130
|
+
# the parent converter ignores the `reasoning`/`reasoning_details` fields OpenRouter adds to the delta
|
|
131
|
+
reasoning = _extract_reasoning(chunk.choices[0].delta) if chunk.choices else None
|
|
132
|
+
has_payload = bool(streaming_chunk.content or streaming_chunk.tool_calls)
|
|
133
|
+
# the last chunk with reasoning, content, or tool calls tells which block the stream is currently in
|
|
134
|
+
last_block = next((c for c in reversed(previous_chunks) if c.reasoning or c.content or c.tool_calls), None)
|
|
135
|
+
|
|
136
|
+
streaming_chunks = []
|
|
137
|
+
if reasoning is not None:
|
|
138
|
+
details = reasoning.extra.get("reasoning_details", [])
|
|
139
|
+
# adopt the index of the first reasoning detail, as the parent does with the first tool call index
|
|
140
|
+
index = next((d["index"] for d in details if isinstance(d.get("index"), int)), 0)
|
|
141
|
+
reasoning_chunk = StreamingChunk(
|
|
142
|
+
content="",
|
|
143
|
+
reasoning=reasoning,
|
|
144
|
+
index=index,
|
|
145
|
+
start=last_block is None or last_block.reasoning is None or last_block.index != index,
|
|
146
|
+
# a reasoning-only delta replaces the parent's empty chunk, so it carries its finish reason
|
|
147
|
+
finish_reason=None if has_payload else streaming_chunk.finish_reason,
|
|
148
|
+
component_info=component_info,
|
|
149
|
+
meta=streaming_chunk.meta,
|
|
150
|
+
)
|
|
151
|
+
if not has_payload:
|
|
152
|
+
return [reasoning_chunk]
|
|
153
|
+
streaming_chunks.append(reasoning_chunk)
|
|
154
|
+
last_block = reasoning_chunk
|
|
155
|
+
|
|
156
|
+
if streaming_chunk.content:
|
|
157
|
+
# the parent converter marks the second chunk of a stream as start, assuming the first one only carries `role`,
|
|
158
|
+
# and gives text chunks index None when `role` is set. OpenRouter sets `role` on every chunk and can stream text
|
|
159
|
+
# in the first one, so derive start and index from the previous block instead. Text after tool calls keeps the
|
|
160
|
+
# parent's start and index, so the whitespace some models stream between tool calls gets no header.
|
|
161
|
+
if last_block is None:
|
|
162
|
+
streaming_chunk = replace(streaming_chunk, start=True)
|
|
163
|
+
elif last_block.reasoning is not None:
|
|
164
|
+
streaming_chunk = replace(streaming_chunk, start=True, index=(last_block.index or 0) + 1)
|
|
165
|
+
elif last_block.content:
|
|
166
|
+
streaming_chunk = replace(streaming_chunk, start=False, index=last_block.index)
|
|
167
|
+
elif streaming_chunk.tool_calls and last_block is not None:
|
|
168
|
+
# the parent converter uses the provider's tool call index as chunk index, which collides with the reasoning
|
|
169
|
+
# or text block before the tool calls, so shift the chunk index past that block. Later tool calls keep the
|
|
170
|
+
# shift of the first one, even if text is streamed between them. `ToolCallDelta.index` keeps the provider's
|
|
171
|
+
# index, which the tool calls are assembled by.
|
|
172
|
+
previous_tool_chunk = next((c for c in reversed(previous_chunks) if c.tool_calls), None)
|
|
173
|
+
if previous_tool_chunk is not None and previous_tool_chunk.tool_calls:
|
|
174
|
+
offset = (previous_tool_chunk.index or 0) - previous_tool_chunk.tool_calls[0].index
|
|
175
|
+
else:
|
|
176
|
+
offset = (last_block.index or 0) + 1
|
|
177
|
+
streaming_chunk = replace(streaming_chunk, index=streaming_chunk.tool_calls[0].index + offset)
|
|
178
|
+
streaming_chunks.append(streaming_chunk)
|
|
179
|
+
return streaming_chunks
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _merge_reasoning_details(details: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
183
|
+
"""
|
|
184
|
+
Merge streamed `reasoning_details` fragments into complete entries.
|
|
185
|
+
|
|
186
|
+
OpenRouter streams each reasoning detail as fragments sharing its `index` and `type`. String payloads are
|
|
187
|
+
concatenated in order, other fields keep their latest non-null value, such as a `signature` that only arrives
|
|
188
|
+
with the last fragment.
|
|
189
|
+
"""
|
|
190
|
+
merged: dict[tuple[Any, Any], dict[str, Any]] = {}
|
|
191
|
+
for detail in details:
|
|
192
|
+
key = (detail.get("index"), detail.get("type"))
|
|
193
|
+
if key not in merged:
|
|
194
|
+
merged[key] = dict(detail)
|
|
195
|
+
continue
|
|
196
|
+
entry = merged[key]
|
|
197
|
+
for field, value in detail.items():
|
|
198
|
+
if field in ("text", "summary", "data") and isinstance(value, str):
|
|
199
|
+
entry[field] = (entry.get(field) or "") + value
|
|
200
|
+
elif value is not None:
|
|
201
|
+
entry[field] = value
|
|
202
|
+
return list(merged.values())
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _convert_openrouter_streaming_chunks_to_chat_message(chunks: list[StreamingChunk]) -> ChatMessage:
|
|
206
|
+
"""Connect OpenRouter streaming chunks into a single ChatMessage, keeping the merged `reasoning_details`."""
|
|
207
|
+
message = _convert_streaming_chunks_to_chat_message(chunks=chunks)
|
|
208
|
+
# the core helper only joins the reasoning text, but OpenRouter expects the details back in multi-turn conversations
|
|
209
|
+
details = [d for c in chunks if c.reasoning for d in c.reasoning.extra.get("reasoning_details", [])]
|
|
210
|
+
if not details:
|
|
211
|
+
return message
|
|
212
|
+
reasoning = ReasoningContent(
|
|
213
|
+
reasoning_text=message.reasoning.reasoning_text if message.reasoning else "",
|
|
214
|
+
extra={"reasoning_details": _merge_reasoning_details(details)},
|
|
215
|
+
)
|
|
216
|
+
return ChatMessage.from_assistant(
|
|
217
|
+
text=message.text, tool_calls=message.tool_calls, meta=message.meta, reasoning=reasoning
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
104
221
|
@component
|
|
105
222
|
class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
106
223
|
"""
|
|
@@ -118,7 +235,8 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
118
235
|
- **Customizability**: Supports all parameters supported by the OpenRouter chat completion endpoint.
|
|
119
236
|
- **Reasoning Support**: Extracts reasoning/thinking content from models that support it
|
|
120
237
|
(e.g., DeepSeek R1, Claude with extended thinking) and stores it in the `ReasoningContent`
|
|
121
|
-
field on `ChatMessage`.
|
|
238
|
+
field on `ChatMessage`. When streaming, reasoning is also passed to the `streaming_callback`
|
|
239
|
+
in `StreamingChunk.reasoning`.
|
|
122
240
|
|
|
123
241
|
This component uses the ChatMessage format for structuring both input and output,
|
|
124
242
|
ensuring coherent and contextually relevant responses in chat-based text generation scenarios.
|
|
@@ -190,7 +308,6 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
190
308
|
- `random_seed`: The seed to use for random sampling.
|
|
191
309
|
- `reasoning`: A dict to configure reasoning/thinking tokens for models that support it.
|
|
192
310
|
Example: `{"effort": "high"}` or `{"max_tokens": 2000}`.
|
|
193
|
-
Reasoning content is only captured for non-streaming requests.
|
|
194
311
|
See [OpenRouter reasoning docs](https://openrouter.ai/docs/use-cases/reasoning-tokens).
|
|
195
312
|
- `response_format`: A JSON schema or a Pydantic model that enforces the structure of the model's response.
|
|
196
313
|
:param tools:
|
|
@@ -321,6 +438,42 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
321
438
|
final_args["response_format"] = response_format
|
|
322
439
|
return final_args
|
|
323
440
|
|
|
441
|
+
def _handle_stream_response(
|
|
442
|
+
self, chat_completion: Stream[ChatCompletionChunk], callback: SyncStreamingCallbackT
|
|
443
|
+
) -> list[ChatMessage]:
|
|
444
|
+
component_info = ComponentInfo.from_component(self)
|
|
445
|
+
chunks: list[StreamingChunk] = []
|
|
446
|
+
for chunk in chat_completion:
|
|
447
|
+
assert len(chunk.choices) <= 1, "Streaming responses should have at most one choice."
|
|
448
|
+
for streaming_chunk in _convert_openrouter_chunk_to_streaming_chunks(
|
|
449
|
+
chunk=chunk, previous_chunks=chunks, component_info=component_info
|
|
450
|
+
):
|
|
451
|
+
chunks.append(streaming_chunk)
|
|
452
|
+
callback(streaming_chunk)
|
|
453
|
+
return [_convert_openrouter_streaming_chunks_to_chat_message(chunks=chunks)]
|
|
454
|
+
|
|
455
|
+
async def _handle_async_stream_response(
|
|
456
|
+
self, chat_completion: AsyncStream[ChatCompletionChunk], callback: StreamingCallbackT
|
|
457
|
+
) -> list[ChatMessage]:
|
|
458
|
+
component_info = ComponentInfo.from_component(self)
|
|
459
|
+
chunks: list[StreamingChunk] = []
|
|
460
|
+
try:
|
|
461
|
+
async for chunk in chat_completion:
|
|
462
|
+
assert len(chunk.choices) <= 1, "Streaming responses should have at most one choice."
|
|
463
|
+
for streaming_chunk in _convert_openrouter_chunk_to_streaming_chunks(
|
|
464
|
+
chunk=chunk, previous_chunks=chunks, component_info=component_info
|
|
465
|
+
):
|
|
466
|
+
chunks.append(streaming_chunk)
|
|
467
|
+
# haystack-ai >= 3.0 also accepts sync callbacks in async runs, so only await async ones
|
|
468
|
+
callback_result = callback(streaming_chunk)
|
|
469
|
+
if inspect.isawaitable(callback_result):
|
|
470
|
+
await callback_result
|
|
471
|
+
except asyncio.CancelledError:
|
|
472
|
+
# shield the close so the stream is released even though the task is being cancelled
|
|
473
|
+
await asyncio.shield(chat_completion.close())
|
|
474
|
+
raise
|
|
475
|
+
return [_convert_openrouter_streaming_chunks_to_chat_message(chunks=chunks)]
|
|
476
|
+
|
|
324
477
|
@component.output_types(replies=list[ChatMessage])
|
|
325
478
|
def run(
|
|
326
479
|
self,
|
|
@@ -340,8 +493,9 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
340
493
|
:param streaming_callback:
|
|
341
494
|
A callback function that is called when a new token is received from the stream.
|
|
342
495
|
:param generation_kwargs:
|
|
343
|
-
Additional keyword arguments for text generation. These
|
|
344
|
-
|
|
496
|
+
Additional keyword arguments for text generation. These are merged per key with the
|
|
497
|
+
`generation_kwargs` passed at initialization: keys provided here take precedence, keys set only
|
|
498
|
+
at initialization are kept.
|
|
345
499
|
For details on OpenRouter API parameters, see
|
|
346
500
|
[OpenRouter docs](https://openrouter.ai/docs/quickstart).
|
|
347
501
|
:param tools: A list of Tool and/or Toolset objects, or a single Toolset for which the model can prepare calls.
|
|
@@ -363,16 +517,6 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
363
517
|
init_callback=self.streaming_callback, runtime_callback=streaming_callback, requires_async=False
|
|
364
518
|
)
|
|
365
519
|
|
|
366
|
-
# Reasoning content is reconstructed from the full response message, which is not available while
|
|
367
|
-
# streaming, so we warn the user that it will not be captured in this mode.
|
|
368
|
-
if streaming_callback is not None:
|
|
369
|
-
merged_kwargs = {**self.generation_kwargs, **(generation_kwargs or {})}
|
|
370
|
-
if merged_kwargs.get("reasoning"):
|
|
371
|
-
logger.warning(
|
|
372
|
-
"Streaming with reasoning is active. Reasoning content will not be captured during "
|
|
373
|
-
"streaming. Use non-streaming mode to extract reasoning content."
|
|
374
|
-
)
|
|
375
|
-
|
|
376
520
|
api_args = self._prepare_api_call(
|
|
377
521
|
messages=messages,
|
|
378
522
|
streaming_callback=streaming_callback,
|
|
@@ -385,7 +529,6 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
385
529
|
chat_completion = getattr(self.client.chat.completions, openai_endpoint)(**api_args) # type: ignore[union-attr]
|
|
386
530
|
|
|
387
531
|
if streaming_callback is not None:
|
|
388
|
-
# streaming uses the inherited handler so reasoning extraction is intentionally skipped
|
|
389
532
|
completions = self._handle_stream_response(chat_completion, streaming_callback)
|
|
390
533
|
else:
|
|
391
534
|
assert isinstance(chat_completion, ChatCompletion), "Unexpected response type for non-streaming request."
|
|
@@ -419,7 +562,9 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
419
562
|
A callback function that is called when a new token is received from the stream.
|
|
420
563
|
Must be a coroutine.
|
|
421
564
|
:param generation_kwargs:
|
|
422
|
-
Additional keyword arguments for text generation.
|
|
565
|
+
Additional keyword arguments for text generation. These are merged per key with the
|
|
566
|
+
`generation_kwargs` passed at initialization: keys provided here take precedence, keys set only
|
|
567
|
+
at initialization are kept.
|
|
423
568
|
:param tools: A list of Tool and/or Toolset objects, or a single Toolset.
|
|
424
569
|
:param tools_strict:
|
|
425
570
|
Whether to enable strict schema adherence for tool calls.
|
|
@@ -442,16 +587,6 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
442
587
|
init_callback=self.streaming_callback, runtime_callback=streaming_callback, requires_async=True
|
|
443
588
|
)
|
|
444
589
|
|
|
445
|
-
# Reasoning content is reconstructed from the full response message, which is not available while
|
|
446
|
-
# streaming, so we warn the user that it will not be captured in this mode.
|
|
447
|
-
if streaming_callback is not None:
|
|
448
|
-
merged_kwargs = {**self.generation_kwargs, **(generation_kwargs or {})}
|
|
449
|
-
if merged_kwargs.get("reasoning"):
|
|
450
|
-
logger.warning(
|
|
451
|
-
"Streaming with reasoning is active. Reasoning content will not be captured during "
|
|
452
|
-
"streaming. Use non-streaming mode to extract reasoning content."
|
|
453
|
-
)
|
|
454
|
-
|
|
455
590
|
api_args = self._prepare_api_call(
|
|
456
591
|
messages=messages,
|
|
457
592
|
streaming_callback=streaming_callback,
|
|
@@ -464,7 +599,6 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
464
599
|
chat_completion = await getattr(self.async_client.chat.completions, openai_endpoint)(**api_args) # type: ignore[union-attr]
|
|
465
600
|
|
|
466
601
|
if streaming_callback is not None:
|
|
467
|
-
# streaming uses the inherited handler so reasoning extraction is intentionally skipped
|
|
468
602
|
completions = await self._handle_async_stream_response(chat_completion, streaming_callback)
|
|
469
603
|
else:
|
|
470
604
|
assert isinstance(chat_completion, ChatCompletion), "Unexpected response type for non-streaming request."
|