lmnr 0.7.15__tar.gz → 0.7.17__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.

Potentially problematic release.


This version of lmnr might be problematic. Click here for more details.

Files changed (103) hide show
  1. {lmnr-0.7.15 → lmnr-0.7.17}/PKG-INFO +2 -1
  2. {lmnr-0.7.15 → lmnr-0.7.17}/pyproject.toml +3 -2
  3. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/litellm/__init__.py +51 -4
  4. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +19 -3
  5. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +50 -0
  6. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/version.py +1 -1
  7. {lmnr-0.7.15 → lmnr-0.7.17}/README.md +0 -0
  8. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/__init__.py +0 -0
  9. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/cli.py +0 -0
  10. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/.flake8 +0 -0
  11. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/__init__.py +0 -0
  12. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/decorators/__init__.py +0 -0
  13. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/litellm/utils.py +0 -0
  14. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +0 -0
  15. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +0 -0
  16. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +0 -0
  17. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_models.py +0 -0
  18. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +0 -0
  19. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +0 -0
  20. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +0 -0
  21. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +0 -0
  22. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py +0 -0
  23. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py +0 -0
  24. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py +0 -0
  25. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py +0 -0
  26. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +0 -0
  27. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +0 -0
  28. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +0 -0
  29. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +0 -0
  30. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +0 -0
  31. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +0 -0
  32. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +0 -0
  33. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +0 -0
  34. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +0 -0
  35. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/utils.py +0 -0
  36. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +0 -0
  37. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +0 -0
  38. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +0 -0
  39. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +0 -0
  40. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +0 -0
  41. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +0 -0
  42. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +0 -0
  43. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +0 -0
  44. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +0 -0
  45. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +0 -0
  46. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +0 -0
  47. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +0 -0
  48. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +0 -0
  49. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +0 -0
  50. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +0 -0
  51. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +0 -0
  52. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openhands_ai/__init__.py +0 -0
  53. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py +0 -0
  54. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py +0 -0
  55. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +0 -0
  56. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/tracing/__init__.py +0 -0
  57. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +0 -0
  58. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/tracing/attributes.py +0 -0
  59. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/tracing/context.py +0 -0
  60. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/tracing/exporter.py +0 -0
  61. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/tracing/instruments.py +0 -0
  62. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/tracing/processor.py +0 -0
  63. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/tracing/tracer.py +0 -0
  64. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/utils/__init__.py +0 -0
  65. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/utils/json_encoder.py +0 -0
  66. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/utils/package_check.py +0 -0
  67. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/opentelemetry_lib/utils/wrappers.py +0 -0
  68. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/py.typed +0 -0
  69. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/__init__.py +0 -0
  70. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/__init__.py +0 -0
  71. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/browser_use_cdp_otel.py +0 -0
  72. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/browser_use_otel.py +0 -0
  73. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/bubus_otel.py +0 -0
  74. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/cdp_utils.py +0 -0
  75. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/patchright_otel.py +0 -0
  76. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/playwright_otel.py +0 -0
  77. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/pw_utils.py +0 -0
  78. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/recorder/record.umd.min.cjs +0 -0
  79. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/browser/utils.py +0 -0
  80. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/asynchronous/async_client.py +0 -0
  81. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/asynchronous/resources/__init__.py +0 -0
  82. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/asynchronous/resources/agent.py +0 -0
  83. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/asynchronous/resources/base.py +0 -0
  84. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/asynchronous/resources/browser_events.py +0 -0
  85. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/asynchronous/resources/evals.py +0 -0
  86. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/asynchronous/resources/evaluators.py +0 -0
  87. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/asynchronous/resources/tags.py +0 -0
  88. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/synchronous/resources/__init__.py +0 -0
  89. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/synchronous/resources/agent.py +0 -0
  90. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/synchronous/resources/base.py +0 -0
  91. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/synchronous/resources/browser_events.py +0 -0
  92. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/synchronous/resources/evals.py +0 -0
  93. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/synchronous/resources/evaluators.py +0 -0
  94. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/synchronous/resources/tags.py +0 -0
  95. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/client/synchronous/sync_client.py +0 -0
  96. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/datasets.py +0 -0
  97. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/decorators.py +0 -0
  98. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/eval_control.py +0 -0
  99. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/evaluations.py +0 -0
  100. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/laminar.py +0 -0
  101. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/log.py +0 -0
  102. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/types.py +0 -0
  103. {lmnr-0.7.15 → lmnr-0.7.17}/src/lmnr/sdk/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lmnr
3
- Version: 0.7.15
3
+ Version: 0.7.17
4
4
  Summary: Python SDK for Laminar
5
5
  Author: lmnr.ai
6
6
  Author-email: lmnr.ai <founders@lmnr.ai>
@@ -26,6 +26,7 @@ Requires-Dist: grpcio>=1
26
26
  Requires-Dist: httpx>=0.24.0
27
27
  Requires-Dist: orjson>=3.0.0
28
28
  Requires-Dist: packaging>=22.0
29
+ Requires-Dist: opentelemetry-instrumentation-threading>=0.57b0
29
30
  Requires-Dist: opentelemetry-instrumentation-alephalpha>=0.47.1 ; extra == 'alephalpha'
30
31
  Requires-Dist: opentelemetry-instrumentation-alephalpha>=0.47.1 ; extra == 'all'
31
32
  Requires-Dist: opentelemetry-instrumentation-bedrock>=0.47.1 ; extra == 'all'
@@ -6,7 +6,7 @@
6
6
 
7
7
  [project]
8
8
  name = "lmnr"
9
- version = "0.7.15"
9
+ version = "0.7.17"
10
10
  description = "Python SDK for Laminar"
11
11
  authors = [
12
12
  { name = "lmnr.ai", email = "founders@lmnr.ai" }
@@ -39,6 +39,7 @@ dependencies = [
39
39
  "httpx (>=0.24.0)",
40
40
  "orjson (>=3.0.0)",
41
41
  "packaging (>=22.0)",
42
+ "opentelemetry-instrumentation-threading>=0.57b0",
42
43
  ]
43
44
  # poetry would auto-generate these based on requires-python, but other build
44
45
  # systems don't, so we need to specify them manually.
@@ -145,4 +146,4 @@ build-backend = "uv_build"
145
146
  members = ["examples/fastapi-app"]
146
147
 
147
148
  [tool.ruff]
148
- target-version = "py310"
149
+ target-version = "py310"
@@ -406,7 +406,15 @@ try:
406
406
  details.get("cached_tokens"),
407
407
  )
408
408
  # TODO: add audio/image/text token details
409
- # TODO: add completion tokens details (reasoning tokens)
409
+ if usage_dict.get("completion_tokens_details"):
410
+ details = usage_dict.get("completion_tokens_details", {})
411
+ details = model_as_dict(details)
412
+ if details.get("reasoning_tokens"):
413
+ set_span_attribute(
414
+ span,
415
+ "gen_ai.usage.reasoning_tokens",
416
+ details.get("reasoning_tokens"),
417
+ )
410
418
 
411
419
  def _process_tool_calls(self, span, tool_calls, choice_index, is_response=True):
412
420
  """Process and set tool call attributes on the span"""
@@ -467,17 +475,56 @@ try:
467
475
  content = message.get("content", "")
468
476
  if content is None:
469
477
  continue
478
+ reasoning_content = message.get("reasoning_content")
479
+ if reasoning_content:
480
+ if isinstance(reasoning_content, str):
481
+ reasoning_content = [
482
+ {
483
+ "type": "text",
484
+ "text": reasoning_content,
485
+ }
486
+ ]
487
+ elif not isinstance(reasoning_content, list):
488
+ reasoning_content = [
489
+ {
490
+ "type": "text",
491
+ "text": str(reasoning_content),
492
+ }
493
+ ]
494
+ else:
495
+ reasoning_content = []
470
496
  if isinstance(content, str):
471
- set_span_attribute(span, f"gen_ai.completion.{i}.content", content)
497
+ if reasoning_content:
498
+ set_span_attribute(
499
+ span,
500
+ f"gen_ai.completion.{i}.content",
501
+ json.dumps(
502
+ reasoning_content
503
+ + [
504
+ {
505
+ "type": "text",
506
+ "text": content,
507
+ }
508
+ ]
509
+ ),
510
+ )
511
+ else:
512
+ set_span_attribute(
513
+ span,
514
+ f"gen_ai.completion.{i}.content",
515
+ content,
516
+ )
472
517
  elif isinstance(content, list):
473
518
  set_span_attribute(
474
- span, f"gen_ai.completion.{i}.content", json.dumps(content)
519
+ span,
520
+ f"gen_ai.completion.{i}.content",
521
+ json.dumps(reasoning_content + content),
475
522
  )
476
523
  else:
477
524
  set_span_attribute(
478
525
  span,
479
526
  f"gen_ai.completion.{i}.content",
480
- json.dumps(model_as_dict(content)),
527
+ json.dumps(reasoning_content + [model_as_dict(content)]),
481
528
  )
482
529
 
483
530
  def _process_content_part(self, content_part: dict) -> dict:
@@ -21,6 +21,7 @@ from .schema_utils import SchemaJSONEncoder, process_schema
21
21
  from .utils import (
22
22
  dont_throw,
23
23
  get_content,
24
+ merge_text_parts,
24
25
  process_content_union,
25
26
  process_stream_chunk,
26
27
  role_from_content_union,
@@ -272,6 +273,16 @@ def _set_response_attributes(span, response: types.GenerateContentResponse):
272
273
 
273
274
  if response.usage_metadata:
274
275
  usage_dict = to_dict(response.usage_metadata)
276
+ candidates_token_count = usage_dict.get("candidates_token_count")
277
+ # unlike OpenAI, and unlike input cached tokens, thinking tokens are
278
+ # not counted as part of candidates token count, so we need to add them
279
+ # separately for consistency with other instrumentations
280
+ thoughts_token_count = usage_dict.get("thoughts_token_count")
281
+ output_token_count = (
282
+ (candidates_token_count or 0) + (thoughts_token_count or 0)
283
+ if candidates_token_count is not None or thoughts_token_count is not None
284
+ else None
285
+ )
275
286
  set_span_attribute(
276
287
  span,
277
288
  gen_ai_attributes.GEN_AI_USAGE_INPUT_TOKENS,
@@ -280,7 +291,7 @@ def _set_response_attributes(span, response: types.GenerateContentResponse):
280
291
  set_span_attribute(
281
292
  span,
282
293
  gen_ai_attributes.GEN_AI_USAGE_OUTPUT_TOKENS,
283
- usage_dict.get("candidates_token_count"),
294
+ output_token_count,
284
295
  )
285
296
  set_span_attribute(
286
297
  span,
@@ -292,6 +303,11 @@ def _set_response_attributes(span, response: types.GenerateContentResponse):
292
303
  SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS,
293
304
  usage_dict.get("cached_content_token_count"),
294
305
  )
306
+ set_span_attribute(
307
+ span,
308
+ SpanAttributes.LLM_USAGE_REASONING_TOKENS,
309
+ thoughts_token_count,
310
+ )
295
311
 
296
312
  if should_send_prompts():
297
313
  set_span_attribute(
@@ -388,7 +404,7 @@ def _build_from_streaming_response(
388
404
  candidates=[
389
405
  {
390
406
  "content": {
391
- "parts": final_parts,
407
+ "parts": merge_text_parts(final_parts),
392
408
  "role": role,
393
409
  },
394
410
  }
@@ -438,7 +454,7 @@ async def _abuild_from_streaming_response(
438
454
  candidates=[
439
455
  {
440
456
  "content": {
441
- "parts": final_parts,
457
+ "parts": merge_text_parts(final_parts),
442
458
  "role": role,
443
459
  },
444
460
  }
@@ -40,6 +40,56 @@ class ProcessChunkResult(TypedDict):
40
40
  model_version: str | None
41
41
 
42
42
 
43
+ def merge_text_parts(
44
+ parts: list[types.PartDict | types.File | types.Part | str],
45
+ ) -> list[types.Part]:
46
+ if not parts:
47
+ return []
48
+
49
+ merged_parts: list[types.Part] = []
50
+ accumulated_text = ""
51
+
52
+ for part in parts:
53
+ # Handle string input - treat as text
54
+ if isinstance(part, str):
55
+ accumulated_text += part
56
+ # Handle File objects - they are not text, so don't merge
57
+ elif isinstance(part, types.File):
58
+ # Flush any accumulated text first
59
+ if accumulated_text:
60
+ merged_parts.append(types.Part(text=accumulated_text))
61
+ accumulated_text = ""
62
+ # Add the File as-is (wrapped in a Part if needed)
63
+ # Note: File objects should be passed through as-is in the original part
64
+ merged_parts.append(part)
65
+ # Handle Part and PartDict (dicts)
66
+ else:
67
+ part_dict = to_dict(part)
68
+
69
+ # Check if this is a text part
70
+ if part_dict.get("text") is not None:
71
+ accumulated_text += part_dict.get("text")
72
+ else:
73
+ # Non-text part (inline_data, function_call, etc.)
74
+ # Flush any accumulated text first
75
+ if accumulated_text:
76
+ merged_parts.append(types.Part(text=accumulated_text))
77
+ accumulated_text = ""
78
+
79
+ # Add the non-text part as-is
80
+ if isinstance(part, types.Part):
81
+ merged_parts.append(part)
82
+ elif isinstance(part, dict):
83
+ # Convert dict to Part object
84
+ merged_parts.append(types.Part(**part_dict))
85
+
86
+ # Don't forget to add any remaining accumulated text
87
+ if accumulated_text:
88
+ merged_parts.append(types.Part(text=accumulated_text))
89
+
90
+ return merged_parts
91
+
92
+
43
93
  def set_span_attribute(span: Span, name: str, value: Any):
44
94
  if value is not None and value != "":
45
95
  span.set_attribute(name, value)
@@ -3,7 +3,7 @@ import httpx
3
3
  from packaging import version
4
4
 
5
5
 
6
- __version__ = "0.7.15"
6
+ __version__ = "0.7.17"
7
7
  PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}"
8
8
 
9
9
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes