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.
Files changed (16) hide show
  1. openrouter_haystack-0.2.1/CHANGELOG.md +25 -0
  2. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/PKG-INFO +1 -1
  3. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/src/haystack_integrations/components/generators/openrouter/chat/chat_generator.py +2 -0
  4. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/tests/test_openrouter_chat_generator.py +392 -55
  5. openrouter_haystack-0.2.0/CHANGELOG.md +0 -9
  6. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/.gitignore +0 -0
  7. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/LICENSE.txt +0 -0
  8. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/README.md +0 -0
  9. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/examples/openrouter_with_tools_example.py +0 -0
  10. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/pydoc/config.yml +0 -0
  11. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/pyproject.toml +0 -0
  12. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/src/haystack_integrations/components/generators/openrouter/__init__.py +0 -0
  13. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/src/haystack_integrations/components/generators/openrouter/chat/__init__.py +0 -0
  14. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/src/haystack_integrations/components/generators/py.typed +0 -0
  15. {openrouter_haystack-0.2.0 → openrouter_haystack-0.2.1}/tests/__init__.py +0 -0
  16. {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.0
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
 
@@ -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 MistralChatGenerator component can run with tools and get a response.
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"]) > 0, "No replies received"
345
+ assert len(results["replies"]) == 1
331
346
 
332
347
  # Find the message with tool calls
333
- tool_message = None
334
- for message in results["replies"]:
335
- if message.tool_call:
336
- tool_message = message
337
- break
338
-
339
- assert tool_message is not None, "No message with tool call found"
340
- assert isinstance(tool_message, ChatMessage), "Tool message is not a ChatMessage instance"
341
- assert ChatMessage.is_from(tool_message, ChatRole.ASSISTANT), "Tool message is not from the assistant"
342
-
343
- tool_call = tool_message.tool_call
344
- assert tool_call.id, "Tool call does not contain value for 'id' key"
345
- assert tool_call.tool_name == "weather"
346
- assert tool_call.arguments == {"city": "Paris"}
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=tool_call),
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 not final_message.tool_call
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?")], generation_kwargs={"tool_choice": "auto"}
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"]) > 0, "No replies received"
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 = None
398
- for message in results["replies"]:
399
- if message.tool_call:
400
- tool_message = message
401
- break
402
-
403
- assert tool_message is not None, "No message with tool call found"
404
- assert isinstance(tool_message, ChatMessage), "Tool message is not a ChatMessage instance"
405
- assert ChatMessage.is_from(tool_message, ChatRole.ASSISTANT), "Tool message is not from the assistant"
406
-
407
- tool_call = tool_message.tool_call
408
- assert tool_call.id, "Tool call does not contain value for 'id' key"
409
- assert tool_call.tool_name == "weather"
410
- assert tool_call.arguments == {"city": "Paris"}
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 MistralChatGenerator component can be used in a pipeline
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
+ }
@@ -1,9 +0,0 @@
1
- # Changelog
2
-
3
- ## [integrations/openrouter-v1.0.0] - 2025-05-19
4
-
5
- ### 🚀 Features
6
-
7
- - Support OpenRouter API as a Chat Generator (#1723)
8
-
9
- <!-- generated by git-cliff -->