Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
44c95fb
feat(otel): surface real finish_reason + sampling params + response.i…
lucasgomide May 26, 2026
e111b49
fix(bedrock): propagate finish_reason + response_id on async paths
lucasgomide May 27, 2026
4cb2ecd
fix(otel): correct streaming finish_reason + bedrock response_id sema…
lucasgomide May 27, 2026
16bd159
refactor(otel): unify LLM event introspection + drop redundant defens…
lucasgomide May 27, 2026
34e8511
chore(otel): drop dead last_chunk variable from async streaming
lucasgomide May 27, 2026
63e7319
fix(otel): coerce non-list stop_sequences to list[str] on LLMCallStar…
lucasgomide May 27, 2026
0797b38
chore: fix ruff lint findings
lucasgomide May 27, 2026
c25543f
refactor(otel): declare sampling params on BaseLLM + honor stop overr…
lucasgomide May 27, 2026
fe34532
fix: widen max_tokens to int | float | None + apply ruff format
lucasgomide May 27, 2026
1f1f958
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide May 27, 2026
44a6d1e
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide May 28, 2026
14de68f
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide May 28, 2026
90ce685
fix(otel): coerce unknown finish_reason / response_id to None instead…
lucasgomide May 28, 2026
0a02e61
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide May 28, 2026
e7654b8
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 1, 2026
92997c4
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 2, 2026
93b227a
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 2, 2026
fe65e07
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 2, 2026
c51c8bc
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 3, 2026
0b2efb6
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 3, 2026
d0036de
fix(otel): extract Azure stream finish_reason/id before usage-continue
lucasgomide Jun 3, 2026
05efc85
fix(otel): report effective max_tokens cap + bedrock structured finis…
lucasgomide Jun 3, 2026
f374920
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 4, 2026
f63abd8
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 4, 2026
ae59dc9
Merge branch 'main' into luzk/finish-reason-oss
lucasgomide Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion lib/crewai/src/crewai/events/types/llm_events.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from enum import Enum
from typing import Any, Literal

from pydantic import BaseModel
from pydantic import BaseModel, field_validator

from crewai.events.base_events import BaseEvent

Expand Down Expand Up @@ -48,6 +48,43 @@ class LLMCallStartedEvent(LLMEventBase):
tools: list[dict[str, Any]] | None = None
callbacks: list[Any] | None = None
available_functions: dict[str, Any] | None = None
# Sampling/request parameters forwarded for OTel GenAI compliance.
# All optional so legacy emitters keep working unchanged.
temperature: float | None = None
top_p: float | None = None
max_tokens: int | float | None = None
stream: bool | None = None
seed: int | None = None
stop_sequences: list[str] | None = None
frequency_penalty: float | None = None
presence_penalty: float | None = None
n: int | None = None

@field_validator("stop_sequences", mode="before")
@classmethod
def _coerce_stop_sequences_to_str_list(cls, value: Any) -> list[str] | None:
"""Normalize stop_sequences to ``list[str] | None``.

Some providers store stop sequences in non-Python-list containers —
e.g. a Vertex AI / Gemini code path can hand back a
``google.protobuf.struct_pb2.ListValue`` or a ``RepeatedScalarContainer``.
Without coercion the OTel SDK falls back to ``str(value)`` when
``gen_ai.request.stop_sequences`` is set, producing the protobuf
textproto repr (``values { string_value: \"...\" }``) instead of a
proper ``Sequence[str]``.

A bare string is treated as a single stop sequence. Anything that
can't be iterated cleanly falls back to ``None`` rather than crashing
event construction.
"""
if value is None:
return None
if isinstance(value, str):
return [value]
try:
return [item if isinstance(item, str) else str(item) for item in value]
except TypeError:
return None


class LLMCallCompletedEvent(LLMEventBase):
Expand All @@ -58,6 +95,23 @@ class LLMCallCompletedEvent(LLMEventBase):
response: Any
call_type: LLMCallType
usage: dict[str, Any] | None = None
finish_reason: str | None = None
response_id: str | None = None

@field_validator("finish_reason", "response_id", mode="before")
@classmethod
def _coerce_non_string_to_none(cls, value: Any) -> str | None:
"""Drop non-string values so test mocks and exotic provider types
(MagicMock, protobuf enums, etc.) never crash event construction.

Provider helpers are best-effort: when extraction returns something
non-string (e.g. a ``MagicMock`` in unit tests), we treat it as
"no value" rather than raising. Downstream telemetry already
handles the missing-attribute case.
"""
if value is None or isinstance(value, str):
return value
return None


class LLMCallFailedEvent(LLMEventBase):
Expand Down
Loading
Loading