Skip to content

Commit 3ba75d5

Browse files
committed
Fix the CI failure.
1 parent 29eeba0 commit 3ba75d5

12 files changed

Lines changed: 115 additions & 51 deletions

File tree

CHANGELOG-loongsuite.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222

2323
- `loongsuite-distro`: initialize loongsuite python agent distro
2424
([#126](https://github.com/alibaba/loongsuite-python-agent/pull/126))
25+
26+
- `loongsuite-instrumentation-litellm`: add support for litellm
27+
([#88](https://github.com/alibaba/loongsuite-python-agent/pull/88))

instrumentation-loongsuite/loongsuite-instrumentation-litellm/README.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Configuration
2222

2323
The instrumentation can be enabled/disabled using environment variables:
2424

25-
* ``ARMS_LITELLM_INSTRUMENTATION_ENABLED``: Enable/disable instrumentation (default: true)
25+
* ``ENABLE_LITELLM_INSTRUMENTOR``: Enable/disable instrumentation (default: true)
2626

2727
Usage
2828
-----
@@ -49,7 +49,6 @@ This instrumentation automatically captures:
4949
* LLM completion calls (sync and async)
5050
* Streaming completions
5151
* Embedding calls
52-
* Image generation calls
5352
* Retry mechanisms
5453
* Tool/function calls
5554
* Request and response metadata

instrumentation-loongsuite/loongsuite-instrumentation-litellm/src/opentelemetry/instrumentation/litellm/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
The instrumentation can be configured using environment variables:
3232
3333
* ``ENABLE_LITELLM_INSTRUMENTOR``: Enable/disable instrumentation (default: true)
34-
* ``ARMS_LITELLM_INSTRUMENTATION_ENABLED``: Alternative enable/disable flag (default: true)
3534
3635
Usage
3736
-----

instrumentation-loongsuite/loongsuite-instrumentation-litellm/src/opentelemetry/instrumentation/litellm/_embedding_wrapper.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
def _is_instrumentation_enabled() -> bool:
3636
"""Check if instrumentation is enabled via environment variable."""
37-
enabled = os.getenv("ARMS_LITELLM_INSTRUMENTATION_ENABLED", "true").lower()
37+
enabled = os.getenv("ENABLE_LITELLM_INSTRUMENTOR", "true").lower()
3838
return enabled != "false"
3939

4040

@@ -60,8 +60,6 @@ def __call__(self, *args, **kwargs):
6060
if get_current_span().get_span_context().is_valid:
6161
return self.original_func(*args, **kwargs)
6262

63-
# Create invocation object
64-
6563
# Create invocation object
6664
invocation = create_embedding_invocation_from_litellm(**kwargs)
6765

@@ -72,6 +70,7 @@ def __call__(self, *args, **kwargs):
7270
context.set_value(SUPPRESS_LLM_SDK_KEY, True)
7371
)
7472
except Exception:
73+
# If context setting fails, continue without suppression token
7574
pass
7675

7776
# Start Embedding invocation
@@ -161,8 +160,6 @@ async def __call__(self, *args, **kwargs):
161160
if get_current_span().get_span_context().is_valid:
162161
return await self.original_func(*args, **kwargs)
163162

164-
# Create invocation object
165-
166163
# Create invocation object
167164
invocation = create_embedding_invocation_from_litellm(**kwargs)
168165

@@ -173,6 +170,7 @@ async def __call__(self, *args, **kwargs):
173170
context.set_value(SUPPRESS_LLM_SDK_KEY, True)
174171
)
175172
except Exception:
173+
# If context setting fails, continue without suppression token
176174
pass
177175

178176
# Start Embedding invocation

instrumentation-loongsuite/loongsuite-instrumentation-litellm/src/opentelemetry/instrumentation/litellm/_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ def convert_litellm_messages_to_genai_format(
248248
try:
249249
arguments = json.loads(arguments)
250250
except Exception:
251+
# If arguments are not valid JSON, keep the original string
251252
pass
252253

253254
parts.append(
@@ -306,6 +307,7 @@ def extract_output_from_litellm_response(response: Any) -> List:
306307
try:
307308
arguments = json.loads(arguments)
308309
except Exception:
310+
# If arguments are not valid JSON, keep the original string
309311
pass
310312

311313
parts.append(

instrumentation-loongsuite/loongsuite-instrumentation-litellm/src/opentelemetry/instrumentation/litellm/_wrapper.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@
4343
logger = logging.getLogger(__name__)
4444

4545
# Environment variable to control instrumentation
46-
LITELLM_INSTRUMENTATION_ENABLED = "ARMS_LITELLM_INSTRUMENTATION_ENABLED"
46+
ENABLE_LITELLM_INSTRUMENTOR = "ENABLE_LITELLM_INSTRUMENTOR"
4747

4848

4949
def _is_instrumentation_enabled() -> bool:
5050
"""Check if instrumentation is enabled via environment variable."""
51-
enabled = os.getenv(LITELLM_INSTRUMENTATION_ENABLED, "true").lower()
51+
enabled = os.getenv(ENABLE_LITELLM_INSTRUMENTOR, "true").lower()
5252
return enabled != "false"
5353

5454

@@ -82,8 +82,6 @@ def __call__(self, *args, **kwargs):
8282
if is_stream and "stream_options" not in kwargs:
8383
kwargs["stream_options"] = {"include_usage": True}
8484

85-
# For streaming, we need special handling
86-
8785
# For streaming, we need special handling
8886
if is_stream:
8987
# Create invocation object
@@ -96,6 +94,7 @@ def __call__(self, *args, **kwargs):
9694
context.set_value(SUPPRESS_LLM_SDK_KEY, True)
9795
)
9896
except Exception:
97+
# If context setting fails, continue without suppression token
9998
pass
10099

101100
# Start LLM invocation
@@ -110,11 +109,14 @@ def __call__(self, *args, **kwargs):
110109
stream_wrapper = StreamWrapper(
111110
stream=response,
112111
span=invocation.span, # For TTFT tracking
113-
callback=lambda span,
112+
callback=None,
113+
)
114+
stream_wrapper.callback = (
115+
lambda span,
114116
last_chunk,
115117
error: self._handle_stream_end_with_handler(
116118
invocation, last_chunk, error, stream_wrapper
117-
),
119+
)
118120
)
119121
response = stream_wrapper
120122

@@ -143,6 +145,7 @@ def __call__(self, *args, **kwargs):
143145
context.set_value(SUPPRESS_LLM_SDK_KEY, True)
144146
)
145147
except Exception:
148+
# If context setting fails, continue without suppression token
146149
pass
147150

148151
# Start LLM invocation (handler creates and manages span)
@@ -242,6 +245,7 @@ def _handle_stream_end_with_handler(
242245
try:
243246
arguments = json.loads(arguments)
244247
except Exception:
248+
# If arguments are not valid JSON, keep the original string
245249
pass
246250

247251
parts.append(
@@ -302,8 +306,12 @@ def _handle_stream_end_with_handler(
302306
self._handler.fail_llm(
303307
invocation, Error(message=str(e), type=type(e))
304308
)
305-
except Exception:
306-
pass
309+
except Exception as handler_error:
310+
# Swallow exceptions from telemetry failure reporting, but log them for diagnostics.
311+
logger.debug(
312+
"Error while reporting LLM failure in _handle_stream_end_with_handler: %s",
313+
handler_error,
314+
)
307315

308316

309317
class AsyncCompletionWrapper:
@@ -347,6 +355,7 @@ async def __call__(self, *args, **kwargs):
347355
context.set_value(SUPPRESS_LLM_SDK_KEY, True)
348356
)
349357
except Exception:
358+
# If context setting fails, continue without suppression token
350359
pass
351360

352361
# Start LLM invocation
@@ -360,11 +369,14 @@ async def __call__(self, *args, **kwargs):
360369
stream_wrapper = AsyncStreamWrapper(
361370
stream=response,
362371
span=invocation.span, # For TTFT tracking
363-
callback=lambda span,
372+
callback=None,
373+
)
374+
stream_wrapper.callback = (
375+
lambda span,
364376
last_chunk,
365377
error: self._handle_stream_end_with_handler(
366378
invocation, last_chunk, error, stream_wrapper
367-
),
379+
)
368380
)
369381
response = stream_wrapper
370382

@@ -394,6 +406,7 @@ async def __call__(self, *args, **kwargs):
394406
context.set_value(SUPPRESS_LLM_SDK_KEY, True)
395407
)
396408
except Exception:
409+
# If context setting fails, continue without suppression token
397410
pass
398411

399412
# Start LLM invocation

instrumentation-loongsuite/loongsuite-instrumentation-litellm/test-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
litellm>=1.79.0
1+
litellm>=1.0.0
22
pytest
33
pytest-asyncio
44
openai

instrumentation-loongsuite/loongsuite-instrumentation-litellm/tests/test_error_handling.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ def setUp(self):
3333
LiteLLMInstrumentor().instrument(
3434
tracer_provider=self.tracer_provider,
3535
)
36+
# Use model aliases
37+
litellm.model_alias_map = {
38+
"qwen-turbo": "openai/qwen-turbo",
39+
"qwen-plus": "openai/qwen-plus",
40+
}
41+
if os.environ.get("DASHSCOPE_API_KEY"):
42+
os.environ["OPENAI_API_KEY"] = os.environ["DASHSCOPE_API_KEY"]
3643

3744
def tearDown(self):
3845
super().tearDown()
@@ -46,23 +53,32 @@ def test_authentication_failure(self):
4653
Test handling of authentication failures.
4754
"""
4855

49-
# Temporarily set invalid credentials
50-
original_dashscope_key = os.environ.get("DASHSCOPE_API_KEY")
51-
os.environ["DASHSCOPE_API_KEY"] = "invalid-key-12345"
56+
# Temporarily set invalid credentials and base to trigger fast failure
57+
original_keys = {
58+
"DASHSCOPE_API_KEY": os.environ.get("DASHSCOPE_API_KEY"),
59+
"OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY"),
60+
"OPENAI_API_BASE": os.environ.get("OPENAI_API_BASE"),
61+
}
62+
os.environ["DASHSCOPE_API_KEY"] = "invalid"
63+
os.environ["OPENAI_API_KEY"] = "invalid"
64+
os.environ["OPENAI_API_BASE"] = "http://localhost:1"
5265

5366
try:
5467
litellm.completion(
55-
model="dashscope/qwen-turbo",
68+
model="qwen-turbo",
5669
messages=[{"role": "user", "content": "Hello"}],
70+
num_retries=0,
5771
)
58-
self.fail("Expected authentication error but call succeeded")
72+
self.fail("Expected failure but call succeeded")
5973
except Exception as e:
6074
self.assertIsNotNone(e)
6175
finally:
62-
if original_dashscope_key:
63-
os.environ["DASHSCOPE_API_KEY"] = original_dashscope_key
64-
else:
65-
os.environ.pop("DASHSCOPE_API_KEY", None)
76+
# Restore original environment
77+
for key, val in original_keys.items():
78+
if val:
79+
os.environ[key] = val
80+
else:
81+
os.environ.pop(key, None)
6682

6783
spans = self.get_finished_spans()
6884
self.assertEqual(len(spans), 1, "Should create 1 span even on error")
@@ -122,7 +138,7 @@ def test_network_timeout(self):
122138

123139
try:
124140
litellm.completion(
125-
model="dashscope/qwen-turbo",
141+
model="qwen-turbo",
126142
messages=[{"role": "user", "content": "Tell me a long story"}],
127143
timeout=0.001,
128144
)
@@ -145,7 +161,7 @@ def test_max_tokens_exceeded(self):
145161
)
146162

147163
response = litellm.completion(
148-
model="dashscope/qwen-turbo",
164+
model="qwen-turbo",
149165
messages=[{"role": "user", "content": "Write a 500 word essay"}],
150166
max_tokens=2,
151167
)

instrumentation-loongsuite/loongsuite-instrumentation-litellm/tests/test_retry.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ def setUp(self):
6969
LiteLLMInstrumentor().instrument(
7070
tracer_provider=self.tracer_provider,
7171
)
72+
# Use model aliases
73+
litellm.model_alias_map = {
74+
"qwen-turbo": "openai/qwen-turbo",
75+
"qwen-plus": "openai/qwen-plus",
76+
}
77+
if os.environ.get("DASHSCOPE_API_KEY"):
78+
os.environ["OPENAI_API_KEY"] = os.environ["DASHSCOPE_API_KEY"]
7279

7380
def tearDown(self):
7481
super().tearDown()
@@ -84,7 +91,7 @@ def test_completion_with_retries_success(self):
8491

8592
# Business demo: Completion with retry wrapper (success case)
8693
response = litellm.completion_with_retries(
87-
model="dashscope/qwen-turbo",
94+
model="qwen-turbo",
8895
messages=[
8996
{"role": "user", "content": "What is 1+1? Answer briefly."}
9097
],
@@ -121,7 +128,8 @@ def test_async_completion_with_retries(self):
121128

122129
async def run_async_retry():
123130
response = await litellm.acompletion_with_retries(
124-
model="dashscope/qwen-turbo",
131+
model="qwen-turbo",
132+
custom_llm_provider="openai",
125133
messages=[{"role": "user", "content": "Name a color."}],
126134
temperature=0.0,
127135
)
@@ -158,7 +166,7 @@ def test_completion_with_custom_retry_config(self):
158166
# This demo sets custom retry parameters
159167
# Note: LiteLLM's retry mechanism might use different parameter names
160168
response = litellm.completion_with_retries(
161-
model="dashscope/qwen-turbo",
169+
model="qwen-turbo",
162170
messages=[
163171
{"role": "user", "content": "What is the capital of China?"}
164172
],
@@ -180,7 +188,7 @@ def test_retry_with_streaming(self):
180188

181189
# Business demo: Streaming completion with retry wrapper
182190
response = litellm.completion_with_retries(
183-
model="dashscope/qwen-turbo",
191+
model="qwen-turbo",
184192
messages=[{"role": "user", "content": "Count to 3."}],
185193
stream=True,
186194
temperature=0.0,

0 commit comments

Comments
 (0)