diff --git a/src/fortifyroot/_vendor/VENDOR_DEPENDENCIES.json b/src/fortifyroot/_vendor/VENDOR_DEPENDENCIES.json index b9c0927..9654231 100644 --- a/src/fortifyroot/_vendor/VENDOR_DEPENDENCIES.json +++ b/src/fortifyroot/_vendor/VENDOR_DEPENDENCIES.json @@ -52,7 +52,7 @@ }, "test": { "anthropic": ">=0.25.2,<0.83.0", - "boto3": ">=1.35.49,<2", + "boto3": ">=1.34.120,<2", "chromadb": ">=0.5.23,<0.6.0", "google-genai": ">=1.58.0,<1.59.0", "langchain": ">=1.0.0,<2.0.0", @@ -83,7 +83,7 @@ "pytest": ">=8.2.2,<9", "pytest-asyncio": ">=0.23.7,<1.4.0", "pytest-recording": ">=0.13.1,<0.14.0", - "pytest-sugar": "==1.1.1", + "pytest-sugar": "==1.0.0", "requests": ">=2.31.0,<3", "sqlalchemy": ">=2.0.31,<3", "vcrpy": ">=8.0.0,<9" diff --git a/src/fortifyroot/_vendor/VENDOR_MANIFEST.json b/src/fortifyroot/_vendor/VENDOR_MANIFEST.json index 7f9cb46..47dba2a 100644 --- a/src/fortifyroot/_vendor/VENDOR_MANIFEST.json +++ b/src/fortifyroot/_vendor/VENDOR_MANIFEST.json @@ -1,9 +1,9 @@ { - "vendored_at": "2026-06-19T15:31:31.520765", + "vendored_at": "2026-06-19T16:42:00.072255", "openllmetry_version": "0.52.6", - "git_commit": "c011bcf0077e", + "git_commit": "c42896177f36", "git_branch": "HEAD", - "git_tag": "fr-v0.52.6.26", + "git_tag": "fr-v0.52.6.27", "instrumentation_package_policy": { "opentelemetry-instrumentation-agno": false, "opentelemetry-instrumentation-alephalpha": false, diff --git a/src/fortifyroot/_vendor/opentelemetry/instrumentation/langchain/callback_handler.py b/src/fortifyroot/_vendor/opentelemetry/instrumentation/langchain/callback_handler.py index 11dc812..45e6eb9 100644 --- a/src/fortifyroot/_vendor/opentelemetry/instrumentation/langchain/callback_handler.py +++ b/src/fortifyroot/_vendor/opentelemetry/instrumentation/langchain/callback_handler.py @@ -74,6 +74,11 @@ from opentelemetry.trace.status import Status, StatusCode from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE +FR_STREAMING_TIME_TO_FIRST_TOKEN_MS = ( + "fortifyroot.llm.streaming.time_to_first_token_ms" +) +FR_STREAMING_TIME_TO_GENERATE_MS = "fortifyroot.llm.streaming.time_to_generate_ms" + def _extract_class_name_from_serialized(serialized: Optional[dict[str, Any]]) -> str: """ @@ -596,6 +601,38 @@ def on_llm_start( else: set_llm_request(span, serialized, prompts, kwargs, self.spans[run_id]) + @dont_throw + def on_llm_new_token( + self, + token: str, + *, + run_id: UUID, + parent_run_id: Union[UUID, None] = None, + chunk: Union[GenerationChunk, ChatGenerationChunk, None] = None, + **kwargs: Any, + ) -> Any: + """Run when a streaming LLM emits a new token.""" + if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY): + return + if not token and chunk is None: + return + if run_id not in self.spans: + return + + span_holder = self.spans[run_id] + if span_holder.streaming_first_token_time is not None: + return + + first_token_time = time.time() + span_holder.streaming_first_token_time = first_token_time + span = span_holder.span + if span.is_recording(): + _set_span_attribute( + span, + FR_STREAMING_TIME_TO_FIRST_TOKEN_MS, + round((first_token_time - span_holder.start_time) * 1000), + ) + @dont_throw def on_llm_end( self, @@ -698,7 +735,15 @@ def on_llm_end( set_chat_response(span, response) # Record duration before ending span - duration = time.time() - self.spans[run_id].start_time + end_time = time.time() + streaming_first_token_time = self.spans[run_id].streaming_first_token_time + if streaming_first_token_time is not None and span.is_recording(): + _set_span_attribute( + span, + FR_STREAMING_TIME_TO_GENERATE_MS, + round((end_time - streaming_first_token_time) * 1000), + ) + duration = end_time - self.spans[run_id].start_time vendor = span.attributes.get(GenAIAttributes.GEN_AI_SYSTEM, "Langchain") self.duration_histogram.record( duration, diff --git a/src/fortifyroot/_vendor/opentelemetry/instrumentation/langchain/span_utils.py b/src/fortifyroot/_vendor/opentelemetry/instrumentation/langchain/span_utils.py index a308368..d05c95a 100644 --- a/src/fortifyroot/_vendor/opentelemetry/instrumentation/langchain/span_utils.py +++ b/src/fortifyroot/_vendor/opentelemetry/instrumentation/langchain/span_utils.py @@ -40,6 +40,7 @@ class SpanHolder: entity_path: str start_time: float = field(default_factory=time.time) request_model: Optional[str] = None + streaming_first_token_time: Optional[float] = None # ST-10 review-round-2 fix (2026-05-11): every context_api.attach() # performed for this span — span-context, suppression, metadata # association_properties — appended here in attach order. ``_end_span``