openrouter-haystack 0.2.0__tar.gz → 0.2.1__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-0.2.1/CHANGELOG.md +25 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/PKG-INFO +1 -1
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/src/haystack_integrations/components/generators/openrouter/chat/chat_generator.py +2 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/tests/test_openrouter_chat_generator.py +392 -55
- openrouter_haystack-0.2.0/CHANGELOG.md +0 -9
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/.gitignore +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/LICENSE.txt +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/README.md +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/examples/openrouter_with_tools_example.py +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/pydoc/config.yml +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/pyproject.toml +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/src/haystack_integrations/components/generators/openrouter/__init__.py +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/src/haystack_integrations/components/generators/openrouter/chat/__init__.py +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/src/haystack_integrations/components/generators/py.typed +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/tests/__init__.py +0 -0
- {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/tests/test_openrouter_chat_generator_async.py +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [integrations/openrouter-v0.2.0] - 2025-07-01
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- Fix openrouter types + add py.typed (#2029)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### 🧹 Chores
|
|
11
|
+
|
|
12
|
+
- Align core-integrations Hatch scripts (#1898)
|
|
13
|
+
- Remove black (#1985)
|
|
14
|
+
|
|
15
|
+
### 🌀 Miscellaneous
|
|
16
|
+
|
|
17
|
+
- Test: Remove `test_check_abnormal_completions` - already tested in Haystack (#1842)
|
|
18
|
+
|
|
19
|
+
## [integrations/openrouter-v1.0.0] - 2025-05-19
|
|
20
|
+
|
|
21
|
+
### 🚀 Features
|
|
22
|
+
|
|
23
|
+
- Support OpenRouter API as a Chat Generator (#1723)
|
|
24
|
+
|
|
25
|
+
<!-- generated by git-cliff -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrouter-haystack
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -172,6 +172,8 @@ class OpenRouterChatGenerator(OpenAIChatGenerator):
|
|
|
172
172
|
openai_formatted_messages = [message.to_openai_dict_format() for message in messages]
|
|
173
173
|
|
|
174
174
|
tools = tools or self.tools
|
|
175
|
+
if isinstance(tools, Toolset):
|
|
176
|
+
tools = list(tools)
|
|
175
177
|
tools_strict = tools_strict if tools_strict is not None else self.tools_strict
|
|
176
178
|
_check_duplicate_tool_names(list(tools or []))
|
|
177
179
|
|
{openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/tests/test_openrouter_chat_generator.py
RENAMED
|
@@ -11,12 +11,27 @@ from haystack.dataclasses import ChatMessage, ChatRole, StreamingChunk, ToolCall
|
|
|
11
11
|
from haystack.tools import Tool
|
|
12
12
|
from haystack.utils.auth import Secret
|
|
13
13
|
from openai import OpenAIError
|
|
14
|
-
from openai.types.chat import ChatCompletion, ChatCompletionMessage
|
|
14
|
+
from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessage
|
|
15
15
|
from openai.types.chat.chat_completion import Choice
|
|
16
|
+
from openai.types.chat.chat_completion_chunk import Choice as ChoiceChunk
|
|
17
|
+
from openai.types.chat.chat_completion_chunk import ChoiceDelta, ChoiceDeltaToolCall, ChoiceDeltaToolCallFunction
|
|
18
|
+
from openai.types.completion_usage import CompletionTokensDetails, CompletionUsage, PromptTokensDetails
|
|
16
19
|
|
|
17
20
|
from haystack_integrations.components.generators.openrouter.chat.chat_generator import OpenRouterChatGenerator
|
|
18
21
|
|
|
19
22
|
|
|
23
|
+
class CollectorCallback:
|
|
24
|
+
"""
|
|
25
|
+
Callback to collect streaming chunks for testing purposes.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.chunks = []
|
|
30
|
+
|
|
31
|
+
def __call__(self, chunk: StreamingChunk) -> None:
|
|
32
|
+
self.chunks.append(chunk)
|
|
33
|
+
|
|
34
|
+
|
|
20
35
|
@pytest.fixture
|
|
21
36
|
def chat_messages():
|
|
22
37
|
return [
|
|
@@ -321,44 +336,46 @@ class TestOpenRouterChatGenerator:
|
|
|
321
336
|
@pytest.mark.integration
|
|
322
337
|
def test_live_run_with_tools_and_response(self, tools):
|
|
323
338
|
"""
|
|
324
|
-
Integration test that the
|
|
339
|
+
Integration test that the OpenRouterChatGenerator component can run with tools and get a response.
|
|
325
340
|
"""
|
|
326
|
-
initial_messages = [ChatMessage.from_user("What's the weather like in Paris?")]
|
|
341
|
+
initial_messages = [ChatMessage.from_user("What's the weather like in Paris and Berlin?")]
|
|
327
342
|
component = OpenRouterChatGenerator(tools=tools)
|
|
328
343
|
results = component.run(messages=initial_messages, generation_kwargs={"tool_choice": "auto"})
|
|
329
344
|
|
|
330
|
-
assert len(results["replies"])
|
|
345
|
+
assert len(results["replies"]) == 1
|
|
331
346
|
|
|
332
347
|
# Find the message with tool calls
|
|
333
|
-
tool_message =
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
assert
|
|
348
|
+
tool_message = results["replies"][0]
|
|
349
|
+
|
|
350
|
+
assert isinstance(tool_message, ChatMessage)
|
|
351
|
+
tool_calls = tool_message.tool_calls
|
|
352
|
+
assert len(tool_calls) == 2
|
|
353
|
+
assert ChatMessage.is_from(tool_message, ChatRole.ASSISTANT)
|
|
354
|
+
|
|
355
|
+
for tool_call in tool_calls:
|
|
356
|
+
assert tool_call.id is not None
|
|
357
|
+
assert isinstance(tool_call, ToolCall)
|
|
358
|
+
assert tool_call.tool_name == "weather"
|
|
359
|
+
|
|
360
|
+
arguments = [tool_call.arguments for tool_call in tool_calls]
|
|
361
|
+
assert sorted(arguments, key=lambda x: x["city"]) == [{"city": "Berlin"}, {"city": "Paris"}]
|
|
347
362
|
assert tool_message.meta["finish_reason"] == "tool_calls"
|
|
348
363
|
|
|
349
364
|
new_messages = [
|
|
350
365
|
initial_messages[0],
|
|
351
366
|
tool_message,
|
|
352
|
-
ChatMessage.from_tool(tool_result="22° C", origin=
|
|
367
|
+
ChatMessage.from_tool(tool_result="22° C and sunny", origin=tool_calls[0]),
|
|
368
|
+
ChatMessage.from_tool(tool_result="16° C and windy", origin=tool_calls[1]),
|
|
353
369
|
]
|
|
354
370
|
# Pass the tool result to the model to get the final response
|
|
355
371
|
results = component.run(new_messages)
|
|
356
372
|
|
|
357
373
|
assert len(results["replies"]) == 1
|
|
358
374
|
final_message = results["replies"][0]
|
|
359
|
-
assert
|
|
375
|
+
assert final_message.is_from(ChatRole.ASSISTANT)
|
|
360
376
|
assert len(final_message.text) > 0
|
|
361
377
|
assert "paris" in final_message.text.lower()
|
|
378
|
+
assert "berlin" in final_message.text.lower()
|
|
362
379
|
|
|
363
380
|
@pytest.mark.skipif(
|
|
364
381
|
not os.environ.get("OPENROUTER_API_KEY", None),
|
|
@@ -369,45 +386,29 @@ class TestOpenRouterChatGenerator:
|
|
|
369
386
|
"""
|
|
370
387
|
Integration test that the OpenRouterChatGenerator component can run with tools and streaming.
|
|
371
388
|
"""
|
|
372
|
-
|
|
373
|
-
class Callback:
|
|
374
|
-
def __init__(self):
|
|
375
|
-
self.responses = ""
|
|
376
|
-
self.counter = 0
|
|
377
|
-
self.tool_calls = []
|
|
378
|
-
|
|
379
|
-
def __call__(self, chunk: StreamingChunk) -> None:
|
|
380
|
-
self.counter += 1
|
|
381
|
-
if chunk.content:
|
|
382
|
-
self.responses += chunk.content
|
|
383
|
-
if chunk.meta.get("tool_calls"):
|
|
384
|
-
self.tool_calls.extend(chunk.meta["tool_calls"])
|
|
385
|
-
|
|
386
|
-
callback = Callback()
|
|
387
|
-
component = OpenRouterChatGenerator(tools=tools, streaming_callback=callback)
|
|
389
|
+
component = OpenRouterChatGenerator(tools=tools, streaming_callback=print_streaming_chunk)
|
|
388
390
|
results = component.run(
|
|
389
|
-
[ChatMessage.from_user("What's the weather like in Paris?")],
|
|
391
|
+
[ChatMessage.from_user("What's the weather like in Paris and Berlin?")],
|
|
392
|
+
generation_kwargs={"tool_choice": "auto"},
|
|
390
393
|
)
|
|
391
394
|
|
|
392
|
-
assert len(results["replies"])
|
|
393
|
-
assert callback.counter > 1, "Streaming callback was not called multiple times"
|
|
394
|
-
assert callback.tool_calls, "No tool calls received in streaming"
|
|
395
|
+
assert len(results["replies"]) == 1
|
|
395
396
|
|
|
396
397
|
# Find the message with tool calls
|
|
397
|
-
tool_message =
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
assert
|
|
398
|
+
tool_message = results["replies"][0]
|
|
399
|
+
|
|
400
|
+
assert isinstance(tool_message, ChatMessage)
|
|
401
|
+
tool_calls = tool_message.tool_calls
|
|
402
|
+
assert len(tool_calls) == 2
|
|
403
|
+
assert ChatMessage.is_from(tool_message, ChatRole.ASSISTANT)
|
|
404
|
+
|
|
405
|
+
for tool_call in tool_calls:
|
|
406
|
+
assert tool_call.id is not None
|
|
407
|
+
assert isinstance(tool_call, ToolCall)
|
|
408
|
+
assert tool_call.tool_name == "weather"
|
|
409
|
+
|
|
410
|
+
arguments = [tool_call.arguments for tool_call in tool_calls]
|
|
411
|
+
assert sorted(arguments, key=lambda x: x["city"]) == [{"city": "Berlin"}, {"city": "Paris"}]
|
|
411
412
|
assert tool_message.meta["finish_reason"] == "tool_calls"
|
|
412
413
|
|
|
413
414
|
@pytest.mark.skipif(
|
|
@@ -417,7 +418,7 @@ class TestOpenRouterChatGenerator:
|
|
|
417
418
|
@pytest.mark.integration
|
|
418
419
|
def test_pipeline_with_openrouter_chat_generator(self, tools):
|
|
419
420
|
"""
|
|
420
|
-
Test that the
|
|
421
|
+
Test that the OpenRouterChatGenerator component can be used in a pipeline
|
|
421
422
|
"""
|
|
422
423
|
pipeline = Pipeline()
|
|
423
424
|
pipeline.add_component("generator", OpenRouterChatGenerator(tools=tools))
|
|
@@ -537,3 +538,339 @@ class TestOpenRouterChatGenerator:
|
|
|
537
538
|
assert loaded_generator.tools[0].name == generator.tools[0].name
|
|
538
539
|
assert loaded_generator.tools[0].description == generator.tools[0].description
|
|
539
540
|
assert loaded_generator.tools[0].parameters == generator.tools[0].parameters
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
class TestChatCompletionChunkConversion:
|
|
544
|
+
def test_handle_stream_response(self):
|
|
545
|
+
openrouter_chunks = [
|
|
546
|
+
ChatCompletionChunk(
|
|
547
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
548
|
+
choices=[
|
|
549
|
+
ChoiceChunk(delta=ChoiceDelta(content="", role="assistant"), index=0, native_finish_reason=None)
|
|
550
|
+
],
|
|
551
|
+
created=1750162525,
|
|
552
|
+
model="openai/gpt-4o-mini",
|
|
553
|
+
object="chat.completion.chunk",
|
|
554
|
+
system_fingerprint="fp_34a54ae93c",
|
|
555
|
+
provider="OpenAI",
|
|
556
|
+
),
|
|
557
|
+
ChatCompletionChunk(
|
|
558
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
559
|
+
choices=[
|
|
560
|
+
ChoiceChunk(
|
|
561
|
+
delta=ChoiceDelta(
|
|
562
|
+
role="assistant",
|
|
563
|
+
tool_calls=[
|
|
564
|
+
ChoiceDeltaToolCall(
|
|
565
|
+
index=0,
|
|
566
|
+
id="call_zznlVyVfK0GJwY28SShJpDCh",
|
|
567
|
+
function=ChoiceDeltaToolCallFunction(arguments="", name="weather"),
|
|
568
|
+
type="function",
|
|
569
|
+
)
|
|
570
|
+
],
|
|
571
|
+
),
|
|
572
|
+
index=0,
|
|
573
|
+
native_finish_reason=None,
|
|
574
|
+
)
|
|
575
|
+
],
|
|
576
|
+
created=1750162525,
|
|
577
|
+
model="openai/gpt-4o-mini",
|
|
578
|
+
object="chat.completion.chunk",
|
|
579
|
+
system_fingerprint="fp_34a54ae93c",
|
|
580
|
+
provider="OpenAI",
|
|
581
|
+
),
|
|
582
|
+
ChatCompletionChunk(
|
|
583
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
584
|
+
choices=[
|
|
585
|
+
ChoiceChunk(
|
|
586
|
+
delta=ChoiceDelta(
|
|
587
|
+
role="assistant",
|
|
588
|
+
tool_calls=[
|
|
589
|
+
ChoiceDeltaToolCall(
|
|
590
|
+
index=0,
|
|
591
|
+
function=ChoiceDeltaToolCallFunction(arguments='{"ci'),
|
|
592
|
+
type="function",
|
|
593
|
+
)
|
|
594
|
+
],
|
|
595
|
+
),
|
|
596
|
+
index=0,
|
|
597
|
+
native_finish_reason=None,
|
|
598
|
+
)
|
|
599
|
+
],
|
|
600
|
+
created=1750162525,
|
|
601
|
+
model="openai/gpt-4o-mini",
|
|
602
|
+
object="chat.completion.chunk",
|
|
603
|
+
system_fingerprint="fp_34a54ae93c",
|
|
604
|
+
provider="OpenAI",
|
|
605
|
+
),
|
|
606
|
+
ChatCompletionChunk(
|
|
607
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
608
|
+
choices=[
|
|
609
|
+
ChoiceChunk(
|
|
610
|
+
delta=ChoiceDelta(
|
|
611
|
+
role="assistant",
|
|
612
|
+
tool_calls=[
|
|
613
|
+
ChoiceDeltaToolCall(
|
|
614
|
+
index=0,
|
|
615
|
+
function=ChoiceDeltaToolCallFunction(arguments='ty": '),
|
|
616
|
+
type="function",
|
|
617
|
+
)
|
|
618
|
+
],
|
|
619
|
+
),
|
|
620
|
+
index=0,
|
|
621
|
+
native_finish_reason=None,
|
|
622
|
+
)
|
|
623
|
+
],
|
|
624
|
+
created=1750162525,
|
|
625
|
+
model="openai/gpt-4o-mini",
|
|
626
|
+
object="chat.completion.chunk",
|
|
627
|
+
system_fingerprint="fp_34a54ae93c",
|
|
628
|
+
provider="OpenAI",
|
|
629
|
+
),
|
|
630
|
+
ChatCompletionChunk(
|
|
631
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
632
|
+
choices=[
|
|
633
|
+
ChoiceChunk(
|
|
634
|
+
delta=ChoiceDelta(
|
|
635
|
+
role="assistant",
|
|
636
|
+
tool_calls=[
|
|
637
|
+
ChoiceDeltaToolCall(
|
|
638
|
+
index=0,
|
|
639
|
+
function=ChoiceDeltaToolCallFunction(arguments='"Paris'),
|
|
640
|
+
type="function",
|
|
641
|
+
)
|
|
642
|
+
],
|
|
643
|
+
),
|
|
644
|
+
index=0,
|
|
645
|
+
native_finish_reason=None,
|
|
646
|
+
)
|
|
647
|
+
],
|
|
648
|
+
created=1750162525,
|
|
649
|
+
model="openai/gpt-4o-mini",
|
|
650
|
+
object="chat.completion.chunk",
|
|
651
|
+
system_fingerprint="fp_34a54ae93c",
|
|
652
|
+
provider="OpenAI",
|
|
653
|
+
),
|
|
654
|
+
ChatCompletionChunk(
|
|
655
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
656
|
+
choices=[
|
|
657
|
+
ChoiceChunk(
|
|
658
|
+
delta=ChoiceDelta(
|
|
659
|
+
role="assistant",
|
|
660
|
+
tool_calls=[
|
|
661
|
+
ChoiceDeltaToolCall(
|
|
662
|
+
index=0,
|
|
663
|
+
function=ChoiceDeltaToolCallFunction(arguments='"}'),
|
|
664
|
+
type="function",
|
|
665
|
+
)
|
|
666
|
+
],
|
|
667
|
+
),
|
|
668
|
+
index=0,
|
|
669
|
+
native_finish_reason=None,
|
|
670
|
+
)
|
|
671
|
+
],
|
|
672
|
+
created=1750162525,
|
|
673
|
+
model="openai/gpt-4o-mini",
|
|
674
|
+
object="chat.completion.chunk",
|
|
675
|
+
system_fingerprint="fp_34a54ae93c",
|
|
676
|
+
provider="OpenAI",
|
|
677
|
+
),
|
|
678
|
+
ChatCompletionChunk(
|
|
679
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
680
|
+
choices=[
|
|
681
|
+
ChoiceChunk(
|
|
682
|
+
delta=ChoiceDelta(
|
|
683
|
+
role="assistant",
|
|
684
|
+
tool_calls=[
|
|
685
|
+
ChoiceDeltaToolCall(
|
|
686
|
+
index=1,
|
|
687
|
+
id="call_Mh1uOyW3Ys4gwydHjNHILHGX",
|
|
688
|
+
function=ChoiceDeltaToolCallFunction(arguments="", name="weather"),
|
|
689
|
+
type="function",
|
|
690
|
+
)
|
|
691
|
+
],
|
|
692
|
+
),
|
|
693
|
+
index=0,
|
|
694
|
+
native_finish_reason=None,
|
|
695
|
+
)
|
|
696
|
+
],
|
|
697
|
+
created=1750162525,
|
|
698
|
+
model="openai/gpt-4o-mini",
|
|
699
|
+
object="chat.completion.chunk",
|
|
700
|
+
service_tier=None,
|
|
701
|
+
system_fingerprint="fp_34a54ae93c",
|
|
702
|
+
usage=None,
|
|
703
|
+
provider="OpenAI",
|
|
704
|
+
),
|
|
705
|
+
ChatCompletionChunk(
|
|
706
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
707
|
+
choices=[
|
|
708
|
+
ChoiceChunk(
|
|
709
|
+
delta=ChoiceDelta(
|
|
710
|
+
role="assistant",
|
|
711
|
+
tool_calls=[
|
|
712
|
+
ChoiceDeltaToolCall(
|
|
713
|
+
index=1,
|
|
714
|
+
id=None,
|
|
715
|
+
function=ChoiceDeltaToolCallFunction(arguments='{"ci'),
|
|
716
|
+
type="function",
|
|
717
|
+
)
|
|
718
|
+
],
|
|
719
|
+
),
|
|
720
|
+
index=0,
|
|
721
|
+
native_finish_reason=None,
|
|
722
|
+
)
|
|
723
|
+
],
|
|
724
|
+
created=1750162525,
|
|
725
|
+
model="openai/gpt-4o-mini",
|
|
726
|
+
object="chat.completion.chunk",
|
|
727
|
+
system_fingerprint="fp_34a54ae93c",
|
|
728
|
+
provider="OpenAI",
|
|
729
|
+
),
|
|
730
|
+
ChatCompletionChunk(
|
|
731
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
732
|
+
choices=[
|
|
733
|
+
ChoiceChunk(
|
|
734
|
+
delta=ChoiceDelta(
|
|
735
|
+
role="assistant",
|
|
736
|
+
tool_calls=[
|
|
737
|
+
ChoiceDeltaToolCall(
|
|
738
|
+
index=1,
|
|
739
|
+
function=ChoiceDeltaToolCallFunction(arguments='ty": '),
|
|
740
|
+
type="function",
|
|
741
|
+
)
|
|
742
|
+
],
|
|
743
|
+
),
|
|
744
|
+
index=0,
|
|
745
|
+
native_finish_reason=None,
|
|
746
|
+
)
|
|
747
|
+
],
|
|
748
|
+
created=1750162525,
|
|
749
|
+
model="openai/gpt-4o-mini",
|
|
750
|
+
object="chat.completion.chunk",
|
|
751
|
+
system_fingerprint="fp_34a54ae93c",
|
|
752
|
+
provider="OpenAI",
|
|
753
|
+
),
|
|
754
|
+
ChatCompletionChunk(
|
|
755
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
756
|
+
choices=[
|
|
757
|
+
ChoiceChunk(
|
|
758
|
+
delta=ChoiceDelta(
|
|
759
|
+
role="assistant",
|
|
760
|
+
tool_calls=[
|
|
761
|
+
ChoiceDeltaToolCall(
|
|
762
|
+
index=1,
|
|
763
|
+
function=ChoiceDeltaToolCallFunction(arguments='"Berli'),
|
|
764
|
+
type="function",
|
|
765
|
+
)
|
|
766
|
+
],
|
|
767
|
+
),
|
|
768
|
+
index=0,
|
|
769
|
+
native_finish_reason=None,
|
|
770
|
+
)
|
|
771
|
+
],
|
|
772
|
+
created=1750162525,
|
|
773
|
+
model="openai/gpt-4o-mini",
|
|
774
|
+
object="chat.completion.chunk",
|
|
775
|
+
system_fingerprint="fp_34a54ae93c",
|
|
776
|
+
provider="OpenAI",
|
|
777
|
+
),
|
|
778
|
+
ChatCompletionChunk(
|
|
779
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
780
|
+
choices=[
|
|
781
|
+
ChoiceChunk(
|
|
782
|
+
delta=ChoiceDelta(
|
|
783
|
+
role="assistant",
|
|
784
|
+
tool_calls=[
|
|
785
|
+
ChoiceDeltaToolCall(
|
|
786
|
+
index=1,
|
|
787
|
+
function=ChoiceDeltaToolCallFunction(arguments='n"}'),
|
|
788
|
+
type="function",
|
|
789
|
+
)
|
|
790
|
+
],
|
|
791
|
+
),
|
|
792
|
+
index=0,
|
|
793
|
+
native_finish_reason=None,
|
|
794
|
+
)
|
|
795
|
+
],
|
|
796
|
+
created=1750162525,
|
|
797
|
+
model="openai/gpt-4o-mini",
|
|
798
|
+
object="chat.completion.chunk",
|
|
799
|
+
system_fingerprint="fp_34a54ae93c",
|
|
800
|
+
provider="OpenAI",
|
|
801
|
+
),
|
|
802
|
+
ChatCompletionChunk(
|
|
803
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
804
|
+
choices=[
|
|
805
|
+
ChoiceChunk(
|
|
806
|
+
delta=ChoiceDelta(content="", role="assistant"),
|
|
807
|
+
finish_reason="tool_calls",
|
|
808
|
+
index=0,
|
|
809
|
+
native_finish_reason="tool_calls",
|
|
810
|
+
)
|
|
811
|
+
],
|
|
812
|
+
created=1750162525,
|
|
813
|
+
model="openai/gpt-4o-mini",
|
|
814
|
+
object="chat.completion.chunk",
|
|
815
|
+
system_fingerprint="fp_34a54ae93c",
|
|
816
|
+
provider="OpenAI",
|
|
817
|
+
),
|
|
818
|
+
ChatCompletionChunk(
|
|
819
|
+
id="gen-1750162525-tc7ParBHvsqd6rYhCDtK",
|
|
820
|
+
choices=[
|
|
821
|
+
ChoiceChunk(
|
|
822
|
+
delta=ChoiceDelta(content="", role="assistant"),
|
|
823
|
+
index=0,
|
|
824
|
+
native_finish_reason=None,
|
|
825
|
+
)
|
|
826
|
+
],
|
|
827
|
+
created=1750162525,
|
|
828
|
+
model="openai/gpt-4o-mini",
|
|
829
|
+
object="chat.completion.chunk",
|
|
830
|
+
usage=CompletionUsage(
|
|
831
|
+
completion_tokens=42,
|
|
832
|
+
prompt_tokens=55,
|
|
833
|
+
total_tokens=97,
|
|
834
|
+
completion_tokens_details=CompletionTokensDetails(reasoning_tokens=0),
|
|
835
|
+
prompt_tokens_details=PromptTokensDetails(cached_tokens=0),
|
|
836
|
+
),
|
|
837
|
+
provider="OpenAI",
|
|
838
|
+
),
|
|
839
|
+
]
|
|
840
|
+
|
|
841
|
+
collector_callback = CollectorCallback()
|
|
842
|
+
llm = OpenRouterChatGenerator(api_key=Secret.from_token("test-api-key"))
|
|
843
|
+
result = llm._handle_stream_response(openrouter_chunks, callback=collector_callback)[0] # type: ignore
|
|
844
|
+
|
|
845
|
+
# Assert text is empty
|
|
846
|
+
assert result.text is None
|
|
847
|
+
|
|
848
|
+
# Verify both tool calls were found and processed
|
|
849
|
+
assert len(result.tool_calls) == 2
|
|
850
|
+
assert result.tool_calls[0].id == "call_zznlVyVfK0GJwY28SShJpDCh"
|
|
851
|
+
assert result.tool_calls[0].tool_name == "weather"
|
|
852
|
+
assert result.tool_calls[0].arguments == {"city": "Paris"}
|
|
853
|
+
assert result.tool_calls[1].id == "call_Mh1uOyW3Ys4gwydHjNHILHGX"
|
|
854
|
+
assert result.tool_calls[1].tool_name == "weather"
|
|
855
|
+
assert result.tool_calls[1].arguments == {"city": "Berlin"}
|
|
856
|
+
|
|
857
|
+
# Verify meta information
|
|
858
|
+
assert result.meta["model"] == "openai/gpt-4o-mini"
|
|
859
|
+
assert result.meta["finish_reason"] == "tool_calls"
|
|
860
|
+
assert result.meta["index"] == 0
|
|
861
|
+
assert result.meta["completion_start_time"] is not None
|
|
862
|
+
assert result.meta["usage"] == {
|
|
863
|
+
"completion_tokens": 42,
|
|
864
|
+
"prompt_tokens": 55,
|
|
865
|
+
"total_tokens": 97,
|
|
866
|
+
"completion_tokens_details": {
|
|
867
|
+
"accepted_prediction_tokens": None,
|
|
868
|
+
"audio_tokens": None,
|
|
869
|
+
"reasoning_tokens": 0,
|
|
870
|
+
"rejected_prediction_tokens": None,
|
|
871
|
+
},
|
|
872
|
+
"prompt_tokens_details": {
|
|
873
|
+
"audio_tokens": None,
|
|
874
|
+
"cached_tokens": 0,
|
|
875
|
+
},
|
|
876
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/examples/openrouter_with_tools_example.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|