litellm-haystack 1.1.2__tar.gz → 2.0.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.
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/CHANGELOG.md +13 -0
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/LICENSE.txt +1 -1
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/PKG-INFO +3 -4
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/pyproject.toml +1 -2
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/src/haystack_integrations/components/generators/litellm/chat/chat_generator.py +11 -12
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/tests/test_litellm_chat_generator.py +56 -13
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/.gitignore +0 -0
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/README.md +0 -0
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/pydoc/config_docusaurus.yml +0 -0
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/src/haystack_integrations/components/generators/litellm/__init__.py +0 -0
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/src/haystack_integrations/components/generators/litellm/chat/__init__.py +0 -0
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/src/haystack_integrations/components/generators/py.typed +0 -0
- {litellm_haystack-1.1.2 → litellm_haystack-2.0.0}/tests/__init__.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/litellm-v1.2.0] - 2026-07-30
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- *(litellm)* Include provider cache tokens in usage metadata (#3678)
|
|
8
|
+
|
|
9
|
+
### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Cap litellm dependency below 1.92.0 (#3601)
|
|
12
|
+
|
|
13
|
+
### 🌀 Miscellaneous
|
|
14
|
+
|
|
15
|
+
|
|
3
16
|
## [integrations/litellm-v1.1.1] - 2026-07-15
|
|
4
17
|
|
|
5
18
|
### 🧹 Chores
|
|
@@ -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: litellm-haystack
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: An integration of LiteLLM AI gateway into the Haystack framework, routing to 100+ LLM providers.
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/litellm#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -12,13 +12,12 @@ Keywords: ai-gateway,haystack,litellm,llm
|
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
20
19
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.11
|
|
22
21
|
Requires-Dist: haystack-ai>=2.30.0
|
|
23
22
|
Requires-Dist: litellm<1.92.0,>=1.84.0
|
|
24
23
|
Description-Content-Type: text/markdown
|
|
@@ -7,7 +7,7 @@ name = "litellm-haystack"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "An integration of LiteLLM AI gateway into the Haystack framework, routing to 100+ LLM providers."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
keywords = ["haystack", "litellm", "llm", "ai-gateway"]
|
|
13
13
|
authors = [{ name = "deepset GmbH", email = "info@deepset.ai" }]
|
|
@@ -15,7 +15,6 @@ classifiers = [
|
|
|
15
15
|
"License :: OSI Approved :: Apache Software License",
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
17
17
|
"Programming Language :: Python",
|
|
18
|
-
"Programming Language :: Python :: 3.10",
|
|
19
18
|
"Programming Language :: Python :: 3.11",
|
|
20
19
|
"Programming Language :: Python :: 3.12",
|
|
21
20
|
"Programming Language :: Python :: 3.13",
|
|
@@ -25,7 +25,7 @@ from haystack.tools import (
|
|
|
25
25
|
flatten_tools_or_toolsets,
|
|
26
26
|
serialize_tools_or_toolset,
|
|
27
27
|
)
|
|
28
|
-
from haystack.utils.auth import Secret
|
|
28
|
+
from haystack.utils.auth import Secret
|
|
29
29
|
from haystack.utils.callable_serialization import deserialize_callable, serialize_callable
|
|
30
30
|
|
|
31
31
|
logger = logging.getLogger(__name__)
|
|
@@ -149,7 +149,9 @@ class LiteLLMChatGenerator:
|
|
|
149
149
|
:param messages: Input messages as ChatMessage instances.
|
|
150
150
|
If a string is provided, it is converted to a list containing a ChatMessage with user role.
|
|
151
151
|
:param streaming_callback: Override the streaming callback for this call.
|
|
152
|
-
:param generation_kwargs:
|
|
152
|
+
:param generation_kwargs: Additional parameters for this call. These are merged per key with the
|
|
153
|
+
`generation_kwargs` passed at initialization: keys provided here take precedence, keys set only
|
|
154
|
+
at initialization are kept.
|
|
153
155
|
:param tools: Override tools for this call.
|
|
154
156
|
:returns: A dict with key ``replies`` containing ChatMessage instances.
|
|
155
157
|
"""
|
|
@@ -186,7 +188,9 @@ class LiteLLMChatGenerator:
|
|
|
186
188
|
:param messages: Input messages as ChatMessage instances.
|
|
187
189
|
If a string is provided, it is converted to a list containing a ChatMessage with user role.
|
|
188
190
|
:param streaming_callback: Override the streaming callback for this call.
|
|
189
|
-
:param generation_kwargs:
|
|
191
|
+
:param generation_kwargs: Additional parameters for this call. These are merged per key with the
|
|
192
|
+
`generation_kwargs` passed at initialization: keys provided here take precedence, keys set only
|
|
193
|
+
at initialization are kept.
|
|
190
194
|
:param tools: Override tools for this call.
|
|
191
195
|
:returns: A dict with key ``replies`` containing ChatMessage instances.
|
|
192
196
|
"""
|
|
@@ -246,7 +250,6 @@ class LiteLLMChatGenerator:
|
|
|
246
250
|
@classmethod
|
|
247
251
|
def from_dict(cls, data: dict[str, Any]) -> "LiteLLMChatGenerator":
|
|
248
252
|
"""Deserialize a component from a dictionary."""
|
|
249
|
-
deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
|
|
250
253
|
deserialize_tools_or_toolset_inplace(data["init_parameters"], key="tools")
|
|
251
254
|
if data["init_parameters"].get("streaming_callback"):
|
|
252
255
|
data["init_parameters"]["streaming_callback"] = deserialize_callable(
|
|
@@ -266,16 +269,12 @@ _FINISH_REASON_MAPPING: dict[str, FinishReason] = {
|
|
|
266
269
|
}
|
|
267
270
|
|
|
268
271
|
|
|
269
|
-
def _extract_usage(obj: Any) -> dict[str,
|
|
272
|
+
def _extract_usage(obj: Any) -> dict[str, Any]:
|
|
270
273
|
"""Pull token usage off a litellm response or chunk, tolerating its absence."""
|
|
271
274
|
usage = getattr(obj, "usage", None)
|
|
272
|
-
if not usage:
|
|
273
|
-
return
|
|
274
|
-
return {
|
|
275
|
-
"prompt_tokens": getattr(usage, "prompt_tokens", 0),
|
|
276
|
-
"completion_tokens": getattr(usage, "completion_tokens", 0),
|
|
277
|
-
"total_tokens": getattr(usage, "total_tokens", 0),
|
|
278
|
-
}
|
|
275
|
+
if usage is not None and hasattr(usage, "model_dump"):
|
|
276
|
+
return usage.model_dump(exclude_none=True)
|
|
277
|
+
return {}
|
|
279
278
|
|
|
280
279
|
|
|
281
280
|
def _build_chat_message(response: Any, choice: Any) -> ChatMessage:
|
|
@@ -13,6 +13,7 @@ import pytest
|
|
|
13
13
|
from haystack.dataclasses import ChatMessage, StreamingChunk
|
|
14
14
|
from haystack.tools import Tool
|
|
15
15
|
from haystack.utils.auth import Secret
|
|
16
|
+
from litellm.types.utils import Usage
|
|
16
17
|
|
|
17
18
|
from haystack_integrations.components.generators.litellm import LiteLLMChatGenerator
|
|
18
19
|
|
|
@@ -29,14 +30,9 @@ def _make_mock_response(content="Hello!", model="openai/gpt-4o", tool_calls=None
|
|
|
29
30
|
choice.index = 0
|
|
30
31
|
choice.finish_reason = "stop"
|
|
31
32
|
|
|
32
|
-
usage = MagicMock()
|
|
33
|
-
usage.prompt_tokens = 10
|
|
34
|
-
usage.completion_tokens = 5
|
|
35
|
-
usage.total_tokens = 15
|
|
36
|
-
|
|
37
33
|
resp = MagicMock()
|
|
38
34
|
resp.choices = [choice]
|
|
39
|
-
resp.usage =
|
|
35
|
+
resp.usage = Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15)
|
|
40
36
|
resp.model = model
|
|
41
37
|
return resp
|
|
42
38
|
|
|
@@ -205,18 +201,20 @@ class TestRun:
|
|
|
205
201
|
call_kwargs = fake_litellm.completion.call_args
|
|
206
202
|
assert call_kwargs.kwargs["api_base"] == "https://proxy.local"
|
|
207
203
|
|
|
208
|
-
def
|
|
209
|
-
gen = LiteLLMChatGenerator(
|
|
204
|
+
def test_run_with_generation_kwargs(self):
|
|
205
|
+
gen = LiteLLMChatGenerator(
|
|
206
|
+
model="openai/gpt-4o", generation_kwargs={"temperature": 0.5, "max_completion_tokens": 100}
|
|
207
|
+
)
|
|
210
208
|
mock_resp = _make_mock_response()
|
|
211
209
|
|
|
212
210
|
fake_litellm = types.ModuleType("litellm")
|
|
213
211
|
fake_litellm.completion = MagicMock(return_value=mock_resp)
|
|
214
212
|
|
|
215
213
|
with mock.patch.dict(sys.modules, {"litellm": fake_litellm}):
|
|
216
|
-
gen.run(messages=[ChatMessage.from_user("hi")], generation_kwargs={"
|
|
214
|
+
gen.run(messages=[ChatMessage.from_user("hi")], generation_kwargs={"temperature": 0.9})
|
|
217
215
|
call_kwargs = fake_litellm.completion.call_args
|
|
218
|
-
assert call_kwargs.kwargs["temperature"] == 0.
|
|
219
|
-
assert call_kwargs.kwargs["
|
|
216
|
+
assert call_kwargs.kwargs["temperature"] == 0.9
|
|
217
|
+
assert call_kwargs.kwargs["max_completion_tokens"] == 100
|
|
220
218
|
|
|
221
219
|
def test_empty_messages_returns_empty(self):
|
|
222
220
|
gen = LiteLLMChatGenerator()
|
|
@@ -235,6 +233,30 @@ class TestRun:
|
|
|
235
233
|
meta = result["replies"][0].meta
|
|
236
234
|
assert meta["usage"]["prompt_tokens"] == 10
|
|
237
235
|
assert meta["usage"]["completion_tokens"] == 5
|
|
236
|
+
assert set(meta["usage"]) == {"prompt_tokens", "completion_tokens", "total_tokens"}
|
|
237
|
+
|
|
238
|
+
def test_cache_tokens_in_meta(self):
|
|
239
|
+
gen = LiteLLMChatGenerator(model="anthropic/claude-sonnet-4-20250514")
|
|
240
|
+
mock_resp = _make_mock_response(model="anthropic/claude-sonnet-4-20250514")
|
|
241
|
+
mock_resp.usage = Usage(
|
|
242
|
+
prompt_tokens=2010,
|
|
243
|
+
completion_tokens=5,
|
|
244
|
+
total_tokens=2015,
|
|
245
|
+
cache_creation_input_tokens=1200,
|
|
246
|
+
cache_read_input_tokens=800,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
fake_litellm = types.ModuleType("litellm")
|
|
250
|
+
fake_litellm.completion = MagicMock(return_value=mock_resp)
|
|
251
|
+
|
|
252
|
+
with mock.patch.dict(sys.modules, {"litellm": fake_litellm}):
|
|
253
|
+
usage = gen.run(messages=[ChatMessage.from_user("hi")])["replies"][0].meta["usage"]
|
|
254
|
+
assert usage["cache_creation_input_tokens"] == 1200
|
|
255
|
+
assert usage["cache_read_input_tokens"] == 800
|
|
256
|
+
assert usage["prompt_tokens"] == 2010
|
|
257
|
+
# litellm also normalizes cache counts into the OpenAI-style nested breakdown
|
|
258
|
+
assert usage["prompt_tokens_details"]["cached_tokens"] == 800
|
|
259
|
+
assert usage["prompt_tokens_details"]["cache_creation_tokens"] == 1200
|
|
238
260
|
|
|
239
261
|
def test_tool_calls_parsed(self):
|
|
240
262
|
tc = MagicMock()
|
|
@@ -258,7 +280,7 @@ class TestRun:
|
|
|
258
280
|
|
|
259
281
|
def test_streaming_content_accumulated(self):
|
|
260
282
|
gen = LiteLLMChatGenerator(model="openai/gpt-4o")
|
|
261
|
-
usage =
|
|
283
|
+
usage = Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15)
|
|
262
284
|
chunks = _make_mock_stream_chunks("Hi!", usage=usage)
|
|
263
285
|
|
|
264
286
|
fake_litellm = types.ModuleType("litellm")
|
|
@@ -443,6 +465,27 @@ class TestAsync:
|
|
|
443
465
|
assert len(result["replies"]) == 1
|
|
444
466
|
assert isinstance(result["replies"][0], ChatMessage)
|
|
445
467
|
|
|
468
|
+
@pytest.mark.asyncio
|
|
469
|
+
async def test_run_async_with_generation_kwargs(self):
|
|
470
|
+
gen = LiteLLMChatGenerator(
|
|
471
|
+
model="openai/gpt-4o", generation_kwargs={"temperature": 0.5, "max_completion_tokens": 100}
|
|
472
|
+
)
|
|
473
|
+
mock_resp = _make_mock_response()
|
|
474
|
+
|
|
475
|
+
fake_litellm = types.ModuleType("litellm")
|
|
476
|
+
captured_kwargs = {}
|
|
477
|
+
|
|
478
|
+
async def _acompletion(**kwargs):
|
|
479
|
+
captured_kwargs.update(kwargs)
|
|
480
|
+
return mock_resp
|
|
481
|
+
|
|
482
|
+
fake_litellm.acompletion = _acompletion
|
|
483
|
+
|
|
484
|
+
with mock.patch.dict(sys.modules, {"litellm": fake_litellm}):
|
|
485
|
+
await gen.run_async(messages=[ChatMessage.from_user("hi")], generation_kwargs={"temperature": 0.9})
|
|
486
|
+
assert captured_kwargs["temperature"] == 0.9
|
|
487
|
+
assert captured_kwargs["max_completion_tokens"] == 100
|
|
488
|
+
|
|
446
489
|
@pytest.mark.asyncio
|
|
447
490
|
async def test_run_async_empty_messages(self):
|
|
448
491
|
gen = LiteLLMChatGenerator(model="openai/gpt-4o")
|
|
@@ -452,7 +495,7 @@ class TestAsync:
|
|
|
452
495
|
@pytest.mark.asyncio
|
|
453
496
|
async def test_run_async_streaming(self):
|
|
454
497
|
gen = LiteLLMChatGenerator(model="openai/gpt-4o")
|
|
455
|
-
usage =
|
|
498
|
+
usage = Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15)
|
|
456
499
|
chunks = _make_mock_stream_chunks("Hi!", usage=usage)
|
|
457
500
|
|
|
458
501
|
async def _aiter():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|